001/*
002 * Copyright 2010-2015 Institut Pasteur.
003 * 
004 * This file is part of Icy.
005 * 
006 * Icy is free software: you can redistribute it and/or modify
007 * it under the terms of the GNU General Public License as published by
008 * the Free Software Foundation, either version 3 of the License, or
009 * (at your option) any later version.
010 * 
011 * Icy is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014 * GNU General Public License for more details.
015 * 
016 * You should have received a copy of the GNU General Public License
017 * along with Icy. If not, see <http://www.gnu.org/licenses/>.
018 */
019package icy.file;
020
021import java.awt.Rectangle;
022import java.io.File;
023import java.io.IOException;
024import java.lang.reflect.InvocationTargetException;
025import java.util.ArrayList;
026import java.util.Arrays;
027import java.util.Collection;
028import java.util.HashMap;
029import java.util.HashSet;
030import java.util.List;
031import java.util.Map;
032import java.util.Map.Entry;
033import java.util.Set;
034import java.util.TreeMap;
035
036import icy.common.exception.UnsupportedFormatException;
037import icy.file.SequenceFileSticher.SequenceFileGroup;
038import icy.file.SequenceFileSticher.SequenceIdent;
039import icy.file.SequenceFileSticher.SequencePosition;
040import icy.gui.dialog.ImporterSelectionDialog;
041import icy.gui.dialog.SeriesSelectionDialog;
042import icy.gui.frame.progress.FailedAnnounceFrame;
043import icy.gui.frame.progress.FileFrame;
044import icy.gui.menu.ApplicationMenu;
045import icy.image.ChannelPosition;
046import icy.image.IcyBufferedImage;
047import icy.image.ImagePosition;
048import icy.image.ImageProvider;
049import icy.main.Icy;
050import icy.plugin.PluginDescriptor;
051import icy.plugin.PluginLauncher;
052import icy.plugin.PluginLoader;
053import icy.preferences.GeneralPreferences;
054import icy.sequence.MetaDataUtil;
055import icy.sequence.Sequence;
056import icy.sequence.SequenceIdImporter;
057import icy.sequence.SequenceImporter;
058import icy.sequence.SequencePersistent;
059import icy.sequence.SequenceUtil;
060import icy.system.IcyExceptionHandler;
061import icy.system.SystemUtil;
062import icy.system.thread.ThreadUtil;
063import icy.type.DataType;
064import icy.type.collection.CollectionUtil;
065import icy.util.OMEUtil;
066import icy.util.StringUtil;
067import icy.util.XMLUtil;
068import loci.formats.FormatException;
069import loci.formats.IFormatReader;
070import loci.formats.ImageReader;
071import loci.formats.meta.MetadataStore;
072import loci.formats.ome.OMEXMLMetadataImpl;
073import ome.xml.meta.OMEXMLMetadata;
074import plugins.kernel.importer.LociImporterPlugin;
075
076/**
077 * Sequence / Image loader class.
078 * 
079 * @author Fabrice de Chaumont & Stephane
080 */
081public class Loader
082{
083    /**
084     * @deprecated Use {@link SequenceFileSticher#groupFiles(SequenceFileImporter, Collection, boolean, FileFrame)} instead
085     */
086    @Deprecated
087    public static class FilePosition extends ChannelPosition
088    {
089        public final String path;
090        public String basePath;
091        int s;
092
093        public FilePosition(String path, String basePath, int s, int t, int z, int c)
094        {
095            super(t, z, c);
096
097            this.s = s;
098            this.path = path;
099            this.basePath = basePath;
100        }
101
102        /**
103         * @deprecated Use {@link #FilePosition(String, String, int, int, int, int)} instead.
104         */
105        @Deprecated
106        public FilePosition(String path, int t, int z, int c)
107        {
108            super(t, z, c);
109
110            this.path = path;
111            basePath = "";
112        }
113
114        public FilePosition(String path)
115        {
116            super();
117
118            this.path = path;
119        }
120
121        public FilePosition(FilePosition fp)
122        {
123            this(fp.path, fp.basePath, fp.s, fp.t, fp.z, fp.c);
124        }
125
126        public int getS()
127        {
128            return s;
129        }
130
131        public void setS(int s)
132        {
133            this.s = s;
134        }
135
136        public void set(int s, int t, int z, int c)
137        {
138            super.set(t, z, c);
139            this.s = s;
140        }
141
142        @Override
143        public int compareTo(ImagePosition o)
144        {
145            if (o instanceof FilePosition)
146            {
147                int result = basePath.compareTo(((FilePosition) o).basePath);
148
149                if (result != 0)
150                    return result;
151
152                final int sp = ((FilePosition) o).s;
153
154                if (s > sp)
155                    return 1;
156                if (s < sp)
157                    return -1;
158            }
159
160            return super.compareTo(o);
161        }
162
163        @Override
164        public String toString()
165        {
166            return "File=" + path + " Position=[S:" + s + " T:" + t + " Z:" + z + " C:" + c + "]";
167        }
168    }
169
170    // private final static Set<String> nonImageExtensions = new
171    // HashSet<String>(CollectionUtil.asList(new String[] {
172    // "xml", "txt", "pdf", "xls", "doc", "docx", "rtf", "exe", "wav", "mp3", "app"}));
173    /**
174     * XML, XLS and TXT file can be image metadata files used to open the whole image, accept it !
175     */
176    private final static Set<String> nonImageExtensions = new HashSet<String>(
177            CollectionUtil.asList(new String[] {"pdf", "doc", "docx", "rtf", "exe", "wav", "mp3", "app"}));
178
179    // keep trace of reported / warned plugin
180    private static Set<String> reportedImporterPlugins = new HashSet<String>();
181    private static Set<String> warnedImporterPlugins = new HashSet<String>();
182
183    private static void handleImporterError(PluginDescriptor plugin, Throwable t)
184    {
185        final String pluginId = plugin.getName() + " " + plugin.getVersion();
186
187        if (t instanceof UnsupportedClassVersionError)
188        {
189            if (!warnedImporterPlugins.contains(pluginId))
190            {
191                // show a specific message in the output console
192                System.err.println("Plugin '" + plugin.getName() + "' " + plugin.getVersion()
193                        + " is not compatible with java " + ((int) Math.floor(SystemUtil.getJavaVersionAsNumber())));
194                System.err.println("You need to install a newer version of java to use it.");
195
196                // add to the list of warned plugins
197                warnedImporterPlugins.add(pluginId);
198            }
199        }
200        else
201        {
202            if (!reportedImporterPlugins.contains(pluginId))
203            {
204                // show a message in the output console
205                IcyExceptionHandler.showErrorMessage(t, false, true);
206                // and send an error report (silent as we don't want a dialog appearing here)
207                IcyExceptionHandler.report(plugin, IcyExceptionHandler.getErrorMessage(t, true));
208
209                // add to the list of warned plugins
210                reportedImporterPlugins.add(pluginId);
211            }
212        }
213    }
214
215    /**
216     * Returns all available resource importer.
217     */
218    public static List<Importer> getImporters()
219    {
220        final List<PluginDescriptor> plugins = PluginLoader.getPlugins(Importer.class);
221        final List<Importer> result = new ArrayList<Importer>();
222
223        for (PluginDescriptor plugin : plugins)
224        {
225            try
226            {
227                // add the importer
228                result.add((Importer) PluginLauncher.create(plugin));
229            }
230            catch (Throwable t)
231            {
232                handleImporterError(plugin, t);
233            }
234        }
235
236        return result;
237    }
238
239    /**
240     * Returns all available resource (non image) importer which take file as input.
241     */
242    public static List<FileImporter> getFileImporters()
243    {
244        final List<PluginDescriptor> plugins = PluginLoader.getPlugins(FileImporter.class);
245        final List<FileImporter> result = new ArrayList<FileImporter>();
246
247        for (PluginDescriptor plugin : plugins)
248        {
249            try
250            {
251                // add the importer
252                result.add((FileImporter) PluginLauncher.create(plugin));
253            }
254            catch (Throwable t)
255            {
256                handleImporterError(plugin, t);
257            }
258        }
259
260        return result;
261    }
262
263    /**
264     * Returns a Map containing the appropriate file importer for the specified file.<br>
265     * A file can be absent from the returned Map when no importer support it.<br>
266     * 
267     * @param importers
268     *        the base list of importer we want to test to open file.
269     * @param paths
270     *        the list of file we want to retrieve importer for.
271     * @param useFirstFound
272     *        if set to <code>true</code> then the first matching importer is automatically selected
273     *        otherwise a dialog appears to let the user to choose the correct importer when
274     *        severals importers match for a file.
275     */
276    public static Map<FileImporter, List<String>> getFileImporters(List<FileImporter> importers, List<String> paths,
277            boolean useFirstFound)
278    {
279        final Map<FileImporter, List<String>> result = new HashMap<FileImporter, List<String>>(importers.size());
280        final Map<String, FileImporter> extensionImporters = new HashMap<String, FileImporter>(importers.size());
281
282        for (String path : paths)
283        {
284            final String ext = FileUtil.getFileExtension(path, false);
285            FileImporter imp;
286
287            // try to get importer from extension first
288            imp = extensionImporters.get(ext);
289
290            // do not exist yet
291            if (imp == null)
292            {
293                // find it
294                imp = getFileImporter(importers, path, useFirstFound);
295                // set the importer for this extension
296                if (imp != null)
297                    extensionImporters.put(ext, imp);
298            }
299
300            // importer found for this path ?
301            if (imp != null)
302            {
303                // retrieve current list of path for this importer
304                List<String> list = result.get(imp);
305
306                // do not exist yet --> create it
307                if (list == null)
308                {
309                    list = new ArrayList<String>();
310                    // set the list for this importer
311                    result.put(imp, list);
312                }
313
314                // add path to the list
315                list.add(path);
316            }
317        }
318
319        return result;
320    }
321
322    /**
323     * Returns a Map containing the appropriate file importer for the specified file.<br>
324     * A file can be absent from the returned Map when no importer support it.<br>
325     * 
326     * @param paths
327     *        the list of file we want to retrieve importer for.
328     * @param useFirstFound
329     *        if set to <code>true</code> then the first matching importer is automatically selected
330     *        otherwise a dialog appears to let the user to choose the correct importer when
331     *        severals importers match for a file.
332     */
333    public static Map<FileImporter, List<String>> getFileImporters(List<String> paths, boolean useFirstFound)
334    {
335        return getFileImporters(getFileImporters(), paths, useFirstFound);
336    }
337
338    /**
339     * Returns all file importer which can open the specified file.
340     */
341    public static List<FileImporter> getFileImporters(List<FileImporter> importers, String path)
342    {
343        final List<FileImporter> result = new ArrayList<FileImporter>(importers.size());
344
345        for (FileImporter importer : importers)
346            if (importer.acceptFile(path))
347                result.add(importer);
348
349        return result;
350    }
351
352    /**
353     * Returns all file importer which can open the specified file.
354     */
355    public static List<FileImporter> getFileImporters(String path)
356    {
357        return getFileImporters(getFileImporters(), path);
358    }
359
360    /**
361     * Returns the appropriate file importer for the specified file.<br>
362     * Returns <code>null</code> if no importer can open the file.
363     * 
364     * @param importers
365     *        the base list of importer we want to test to open file.
366     * @param path
367     *        the file we want to retrieve importer for.
368     * @param useFirstFound
369     *        if set to <code>true</code> then the first matching importer is automatically selected
370     *        otherwise a dialog appears to let the user to choose the correct importer when
371     *        severals importers match.
372     * @see #getFileImporters(List, String)
373     */
374    public static FileImporter getFileImporter(List<FileImporter> importers, String path, boolean useFirstFound)
375    {
376        final List<FileImporter> result = new ArrayList<FileImporter>(importers.size());
377
378        for (FileImporter importer : importers)
379        {
380            if (importer.acceptFile(path))
381            {
382                if (useFirstFound)
383                    return importer;
384
385                result.add(importer);
386            }
387        }
388
389        // let user select the good importer
390        return selectFileImporter(result, path);
391    }
392
393    /**
394     * Returns the appropriate file importer for the specified file.<br>
395     * Returns <code>null</code> if no importer can open the file.
396     * 
397     * @param path
398     *        the file we want to retrieve importer for.
399     * @param useFirstFound
400     *        if set to <code>true</code> then the first matching importer is automatically selected
401     *        otherwise a dialog appears to let the user to choose the correct importer when
402     *        severals importers match.
403     * @see #getFileImporters(String)
404     */
405    public static FileImporter getFileImporter(String path, boolean useFirstFound)
406    {
407        return getFileImporter(getFileImporters(), path, useFirstFound);
408    }
409
410    /**
411     * Display a dialog to let the user select the appropriate file importer for the specified file.
412     */
413    public static FileImporter selectFileImporter(final List<FileImporter> importers, final String path)
414    {
415        if (importers.size() == 0)
416            return null;
417        if (importers.size() == 1)
418            return importers.get(0);
419
420        if (Icy.getMainInterface().isHeadLess())
421            return importers.get(0);
422
423        final Object result[] = new Object[1];
424
425        // use invokeNow carefully !
426        ThreadUtil.invokeNow(new Runnable()
427        {
428            @Override
429            public void run()
430            {
431                // get importer
432                result[0] = new ImporterSelectionDialog(importers, path).getSelectedImporter();
433            }
434        });
435
436        return (FileImporter) result[0];
437    }
438
439    /**
440     * Returns all available sequence importer (different from {@link SequenceIdImporter} or {@link SequenceFileImporter}).
441     */
442    public static List<SequenceImporter> getSequenceImporters()
443    {
444        final List<PluginDescriptor> plugins = PluginLoader.getPlugins(SequenceImporter.class);
445        final List<SequenceImporter> result = new ArrayList<SequenceImporter>();
446
447        for (PluginDescriptor plugin : plugins)
448        {
449            try
450            {
451                // add the importer
452                result.add((SequenceImporter) PluginLauncher.create(plugin));
453            }
454            catch (Throwable t)
455            {
456                handleImporterError(plugin, t);
457            }
458        }
459
460        return result;
461    }
462
463    /**
464     * Returns all available sequence importer which take path as input.<br>
465     * If you want to get specifically importer which use path as input, then you need to use {@link #getSequenceFileImporters()} instead.
466     * 
467     * @see #getSequenceFileImporters()
468     */
469    public static List<SequenceIdImporter> getSequenceIdImporters()
470    {
471        final List<PluginDescriptor> plugins = PluginLoader.getPlugins(SequenceIdImporter.class);
472        final List<SequenceIdImporter> result = new ArrayList<SequenceIdImporter>();
473
474        for (PluginDescriptor plugin : plugins)
475        {
476            try
477            {
478                // add the importer
479                result.add((SequenceIdImporter) PluginLauncher.create(plugin));
480            }
481            catch (Throwable t)
482            {
483                handleImporterError(plugin, t);
484            }
485        }
486
487        return result;
488    }
489
490    /**
491     * Returns all available sequence importer which take file as input.
492     * 
493     * @see #getSequenceIdImporters()
494     */
495    public static List<SequenceFileImporter> getSequenceFileImporters()
496    {
497        final List<PluginDescriptor> plugins = PluginLoader.getPlugins(SequenceFileImporter.class);
498        final List<SequenceFileImporter> result = new ArrayList<SequenceFileImporter>();
499
500        for (PluginDescriptor plugin : plugins)
501        {
502            try
503            {
504                // add the importer
505                result.add((SequenceFileImporter) PluginLauncher.create(plugin));
506            }
507            catch (Throwable t)
508            {
509                handleImporterError(plugin, t);
510            }
511        }
512
513        return result;
514    }
515
516    /**
517     * Returns a Map containing the appropriate sequence file importer for the specified list of file path.<br>
518     * A file can be absent from the returned Map when no importer support it.<br>
519     * 
520     * @param importers
521     *        the base list of importer we want to test to open file.
522     * @param paths
523     *        the list of path we want to find importer for.
524     * @param useFirstFound
525     *        if set to <code>true</code> then the first matching importer is automatically selected
526     *        otherwise a dialog appears to let the user to choose the correct importer when
527     *        severals importers match for a path.
528     */
529    @SuppressWarnings("resource")
530    public static <T extends SequenceFileImporter> Map<T, List<String>> getSequenceFileImporters(List<T> importers,
531            List<String> paths, boolean useFirstFound)
532    {
533        final Map<T, List<String>> result = new HashMap<T, List<String>>(importers.size());
534        final Map<String, T> extensionImporters = new HashMap<String, T>(importers.size());
535        T imp = null;
536
537        for (String path : paths)
538        {
539            // get path extension (useful for path path type)
540            final String ext = FileUtil.getFileExtension(path, false);
541
542            // have an extension ? --> try to get importer from extension first
543            if (!StringUtil.isEmpty(ext))
544                imp = extensionImporters.get(ext);
545
546            // have an importer for this path extension ? --> test it
547            if ((imp != null) && !imp.acceptFile(path))
548                imp = null;
549
550            // do not exist yet
551            if (imp == null)
552            {
553                // find it
554                imp = getSequenceFileImporter(importers, path, useFirstFound);
555                // set the importer for this extension
556                if ((imp != null) && !StringUtil.isEmpty(ext))
557                    extensionImporters.put(ext, imp);
558            }
559
560            // importer found for this path ?
561            if (imp != null)
562            {
563                // retrieve current list of path for this importer
564                List<String> list = result.get(imp);
565
566                // do not exist yet --> create it
567                if (list == null)
568                {
569                    list = new ArrayList<String>();
570                    // set the list for this importer
571                    result.put(imp, list);
572                }
573
574                // add path to the list
575                list.add(path);
576            }
577        }
578
579        return result;
580    }
581
582    /**
583     * Returns a Map containing the appropriate sequence file importer for the specified file.<br>
584     * A file can be absent from the returned Map when no importer support it.<br>
585     * 
586     * @param paths
587     *        the list of file we want to retrieve importer for.
588     * @param grouped
589     *        if set to <code>true</code> then we want to group the files so we only need to find the first appropriate importer
590     *        and don't test for other files (only 1 importer will be returned for all files).
591     * @param useFirstFound
592     *        if set to <code>true</code> then the first matching importer is automatically selected
593     *        otherwise a dialog appears to let the user to choose the correct importer when severals importers match for a file.
594     */
595    @SuppressWarnings("resource")
596    public static Map<SequenceFileImporter, List<String>> getSequenceFileImporters(SequenceFileImporter defaultImporter,
597            List<String> paths, boolean grouped, boolean useFirstFound)
598    {
599        if (paths.isEmpty())
600            return new HashMap<SequenceFileImporter, List<String>>();
601
602        final Map<SequenceFileImporter, List<String>> result;
603
604        // have a default importer specified ? --> use it for all files
605        if (defaultImporter != null)
606        {
607            result = new HashMap<SequenceFileImporter, List<String>>();
608            result.put(defaultImporter, paths);
609            return result;
610        }
611
612        // grouped ? --> find the first valid importer
613        if (grouped)
614        {
615            result = new HashMap<SequenceFileImporter, List<String>>();
616
617            for (String path : paths)
618            {
619                final SequenceFileImporter imp = getSequenceFileImporter(path, useFirstFound);
620
621                if (imp != null)
622                {
623                    // use first valid importer for all files
624                    result.put(imp, paths);
625                    break;
626                }
627            }
628
629            return result;
630        }
631
632        // get importers for each path
633        return getSequenceFileImporters(paths, useFirstFound);
634    }
635
636    /**
637     * Returns a Map containing the appropriate sequence file importer for the specified list of file path.<br>
638     * A path can be absent from the returned Map when no importer support it.<br>
639     * 
640     * @param paths
641     *        the list of path we want to retrieve importer for.
642     * @param useFirstFound
643     *        if set to <code>true</code> then the first matching importer is automatically selected
644     *        otherwise a dialog appears to let the user to choose the correct importer when
645     *        severals importers match for a file.
646     */
647    public static Map<SequenceFileImporter, List<String>> getSequenceFileImporters(List<String> paths,
648            boolean useFirstFound)
649    {
650        return getSequenceFileImporters(getSequenceFileImporters(), paths, useFirstFound);
651    }
652
653    /**
654     * Returns all sequence file importer which can open the specified path.
655     */
656    public static <T extends SequenceFileImporter> List<T> getSequenceFileImporters(List<T> importers, String path)
657    {
658        final List<T> result = new ArrayList<T>(importers.size());
659
660        for (T importer : importers)
661            if (importer.acceptFile(path))
662                result.add(importer);
663
664        return result;
665    }
666
667    /**
668     * Returns all sequence file importer which can open the specified file path.
669     */
670    public static List<SequenceFileImporter> getSequenceFileImporters(String path)
671    {
672        return getSequenceFileImporters(getSequenceFileImporters(), path);
673    }
674
675    /**
676     * Returns the appropriate sequence file importer for the specified path.<br>
677     * Depending the parameters it will open a dialog to let the user choose the importer to use
678     * when severals match.<br>
679     * Returns <code>null</code> if no importer can open the specified path.
680     * 
681     * @param importers
682     *        the base list of importer we want to test to open file path.
683     * @param path
684     *        the path we want to retrieve importer for.
685     * @param useFirstFound
686     *        if set to <code>true</code> then the first matching importer is automatically selected
687     *        otherwise a dialog appears to let the user to choose the correct importer when
688     *        severals importers match.
689     * @see #getSequenceFileImporters(List, String)
690     */
691    public static <T extends SequenceFileImporter> T getSequenceFileImporter(List<T> importers, String path,
692            boolean useFirstFound)
693    {
694        final List<T> result = new ArrayList<T>(importers.size());
695
696        for (T importer : importers)
697        {
698            if (importer.acceptFile(path))
699            {
700                if (useFirstFound)
701                    return importer;
702
703                result.add(importer);
704            }
705        }
706
707        // let user select the good importer
708        return selectSequenceFileImporter(result, path);
709    }
710
711    /**
712     * Returns the appropriate sequence file importer for the specified file path.<br>
713     * Depending the parameters it will open a dialog to let the user choose the importer to use
714     * when severals match.<br>
715     * Returns <code>null</code> if no importer can open the specified file path.
716     * 
717     * @param path
718     *        the file path we want to retrieve importer for.
719     * @param useFirstFound
720     *        if set to <code>true</code> then the first matching importer is automatically selected
721     *        otherwise a dialog appears to let the user to choose the correct importer when
722     *        severals importers match.
723     * @see #getSequenceFileImporters(String)
724     */
725    public static SequenceFileImporter getSequenceFileImporter(String path, boolean useFirstFound)
726    {
727        return getSequenceFileImporter(getSequenceFileImporters(), path, useFirstFound);
728    }
729
730    /**
731     * Display a dialog to let the user select the appropriate sequence file importer for the given file path.
732     */
733    @SuppressWarnings("unchecked")
734    public static <T extends SequenceFileImporter> T selectSequenceFileImporter(final List<T> importers,
735            final String path)
736    {
737        if (importers.size() == 0)
738            return null;
739        if (importers.size() == 1)
740            return importers.get(0);
741
742        // no choice, take first one
743        if (Icy.getMainInterface().isHeadLess())
744            return importers.get(0);
745
746        final Object result[] = new Object[1];
747
748        // use invokeNow carefully !
749        ThreadUtil.invokeNow(new Runnable()
750        {
751            @Override
752            public void run()
753            {
754                // get importer
755                final ImporterSelectionDialog selectionDialog = new ImporterSelectionDialog(importers, path);
756
757                if (!selectionDialog.isCanceled())
758                    result[0] = selectionDialog.getSelectedImporter();
759                else
760                    result[0] = null;
761            }
762        });
763
764        return (T) result[0];
765    }
766
767    /**
768     * @deprecated Use {@link #getSequenceFileImporter(List, String, boolean)}
769     */
770    @Deprecated
771    public static SequenceFileImporter getSequenceFileImporter(List<SequenceFileImporter> importers, String path)
772    {
773        return getSequenceFileImporter(importers, path, true);
774    }
775
776    /**
777     * @deprecated Use {@link #getSequenceFileImporter(String, boolean)}
778     */
779    @Deprecated
780    public static SequenceFileImporter getSequenceFileImporter(String path)
781    {
782        return getSequenceFileImporter(path, true);
783    }
784
785    static SequenceFileImporter cloneSequenceFileImporter(SequenceFileImporter importer)
786            throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException,
787            NoSuchMethodException, SecurityException
788    {
789        if (importer == null)
790            return null;
791
792        final SequenceFileImporter result = importer.getClass().getDeclaredConstructor().newInstance();
793
794        if (result instanceof LociImporterPlugin)
795        {
796            final LociImporterPlugin srcImp = (LociImporterPlugin) importer;
797            final LociImporterPlugin resImp = (LociImporterPlugin) result;
798
799            resImp.setGroupFiles(srcImp.isGroupFiles());
800            resImp.setReadOriginalMetadata(srcImp.getReadOriginalMetadata());
801        }
802
803        return result;
804    }
805
806    /**
807     * Returns <code>true</code> if the specified path describes a file type (from extension) which
808     * is well known to
809     * not be an image file.<br>
810     * For instance <i>.exe</i>, <i>.wav</i> or <i>.doc</i> file cannot specify an image file so we
811     * can quickly discard them (extension based exclusion)
812     */
813    public static boolean canDiscardImageFile(String path)
814    {
815        final String ext = FileUtil.getFileExtension(path, false).toLowerCase();
816
817        return nonImageExtensions.contains(ext);
818    }
819
820    /**
821     * Returns true if the specified file is a supported image file.
822     */
823    public static boolean isSupportedImageFile(String path)
824    {
825        return getSequenceFileImporter(path, true) != null;
826    }
827
828    /**
829     * @deprecated Use {@link #isSupportedImageFile(String)} instead.
830     */
831    @Deprecated
832    public static boolean isImageFile(String path)
833    {
834        return isSupportedImageFile(path);
835    }
836
837    /**
838     * Returns path which are supported by the specified imported for the given list of paths.
839     */
840    public static List<String> getSupportedFiles(SequenceFileImporter importer, List<String> paths)
841    {
842        final List<String> result = new ArrayList<String>();
843
844        for (String path : paths)
845        {
846            if (importer.acceptFile(path))
847                result.add(path);
848        }
849
850        return result;
851    }
852
853    /**
854     * Check if we can open the given image plane resolution (XY size < 2^31).<br>
855     * If the image plane is too large the method throw an exception with an informative error
856     * message about the encountered limitation.
857     * 
858     * @param resolution
859     *        wanted image resolution: a value of <code>0</code> means full resolution of the
860     *        original image while value <code>1</code> correspond to the resolution / 2.<br>
861     *        Formula: <code>resolution / 2^value</code><br>
862     * @param sizeX
863     *        width of the image region we want to load
864     * @param sizeY
865     *        height of the image region we want to load
866     * @param messageSuffix
867     *        message suffix for the exception if wanted
868     * @throws UnsupportedOperationException
869     *         if the XY plane size is >= 2^31 pixels
870     * @return the number of pixels of the image plane
871     */
872    public static long checkOpeningPlane(int resolution, int sizeX, int sizeY, String messageSuffix)
873            throws UnsupportedOperationException
874    {
875        // size of XY plane
876        long sizeXY = (long) sizeX * (long) sizeY;
877        // wanted resolution
878        sizeXY /= Math.pow(4, resolution);
879
880        // we can't handle that plane size
881        if (sizeXY > Integer.MAX_VALUE)
882            throw new UnsupportedOperationException(
883                    "Cannot open image with a XY plane size >= 2^31." + ((messageSuffix != null) ? messageSuffix : ""));
884
885        return sizeXY;
886    }
887
888    /**
889     * Check if we have enough resource to open the image defined by the given size information and
890     * wanted resolution.<br>
891     * If the image is too large to be displayed at full resolution (XY plane size > 2^31) or if we
892     * don't have enough
893     * memory to store the whole image the method throw an exception with an informative error
894     * message about the
895     * encountered limitation.
896     * 
897     * @param resolution
898     *        wanted image resolution: a value of <code>0</code> means full resolution of the
899     *        original image while value <code>1</code> correspond to the resolution / 2.<br>
900     *        Formula: <code>resolution / 2^value</code><br>
901     * @param sizeX
902     *        width of the image region we want to load
903     * @param sizeY
904     *        height of the image region we want to load
905     * @param sizeC
906     *        number of channel we want to load
907     * @param sizeZ
908     *        number of slice we want to load (can be different from original image sizeZ)
909     * @param sizeT
910     *        number of frame we want to load (can be different from original image sizeT)
911     * @param dataType
912     *        pixel data type of the image we want to load
913     * @param messageSuffix
914     *        message suffix for the exception if wanted
915     * @throws UnsupportedOperationException
916     *         if the XY plane size is >= 2^31 pixels
917     * @throws OutOfMemoryError
918     *         if there is not enough memory to open the image
919     */
920    public static void checkOpening(int resolution, int sizeX, int sizeY, int sizeC, int sizeZ, int sizeT,
921            DataType dataType, String messageSuffix) throws UnsupportedOperationException, OutOfMemoryError
922    {
923        final long sizeXY = checkOpeningPlane(resolution, sizeX, sizeY, messageSuffix);
924
925        // get free memory
926        long freeInByte = SystemUtil.getJavaFreeMemory() - (16 * 1024 * 1024);
927        // check that we have enough memory for the whole image and for the ARGB image used for
928        // display (sizeXY * 4)
929        long sizeInByte = (sizeXY * sizeC * sizeZ * sizeT * dataType.getSize()) + (sizeXY * 4);
930
931        // not enough memory to store the whole image ?
932        if (sizeInByte > freeInByte)
933        {
934            // try to release some memory
935            System.gc();
936            // get updated free memory
937            freeInByte = SystemUtil.getJavaFreeMemory() - (16 * 1024 * 1024);
938        }
939
940        // still not enough memory ?
941        if (sizeInByte > freeInByte)
942            throw new OutOfMemoryError("Not enough memory to open the wanted image resolution."
943                    + ((messageSuffix != null) ? messageSuffix : ""));
944    }
945
946    /**
947     * Check if we have enough resource to open the image defined by the given metadata information, series index and
948     * wanted resolution.<br>
949     * If the image is too large to be displayed at full resolution (XY plane size > 2^31) or if we* don't have enough
950     * memory to store the whole image the method throw an exception with an informative error message about the
951     * encountered limitation.
952     * 
953     * @param meta
954     *        metadata of the image
955     * @param series
956     *        series index
957     * @param resolution
958     *        wanted image resolution: a value of <code>0</code> means full resolution of the
959     *        original image while value
960     *        <code>1</code> correspond to the resolution / 2.<br>
961     *        Formula: <code>resolution / 2^value</code><br>
962     * @param sizeZ
963     *        number of slice we want to load (can be different from original image sizeZ)
964     * @param sizeT
965     *        number of frame we want to load (can be different from original image sizeT)
966     * @param messageSuffix
967     *        message suffix for the exception if wanted
968     * @throws UnsupportedOperationException
969     *         if the XY plane size is >= 2^31 pixels
970     * @throws OutOfMemoryError
971     *         if there is not enough memory to open the image
972     */
973    public static void checkOpening(OMEXMLMetadata meta, int series, int resolution, int sizeZ, int sizeT,
974            String messageSuffix) throws UnsupportedOperationException, OutOfMemoryError
975    {
976        checkOpening(resolution, MetaDataUtil.getSizeX(meta, series), MetaDataUtil.getSizeY(meta, series),
977                MetaDataUtil.getSizeC(meta, series), sizeZ, sizeT, MetaDataUtil.getDataType(meta, series),
978                messageSuffix);
979    }
980
981    /**
982     * Check if we have enough resource to open the image defined by the given metadata information, series index and
983     * wanted resolution.<br>
984     * If the image is too large to be displayed at full resolution (XY plane size > 2^31) or if we don't have enough
985     * memory to store the whole image the method throw an exception with an informative error message about the
986     * encountered limitation.
987     * 
988     * @param meta
989     *        metadata of the image
990     * @param series
991     *        series index
992     * @param resolution
993     *        wanted image resolution: a value of <code>0</code> means full resolution of the
994     *        original image while value
995     *        <code>1</code> correspond to the resolution / 2.<br>
996     *        Formula: <code>resolution / 2^value</code><br>
997     * @param messageSuffix
998     *        message suffix for the exception if wanted
999     * @throws UnsupportedOperationException
1000     *         if the XY plane size is >= 2^31 pixels
1001     * @throws OutOfMemoryError
1002     *         if there is not enough memory to open the image
1003     */
1004    public static void checkOpening(OMEXMLMetadata meta, int series, int resolution, String messageSuffix)
1005            throws UnsupportedOperationException, OutOfMemoryError
1006    {
1007        checkOpening(meta, series, resolution, MetaDataUtil.getSizeZ(meta, series), MetaDataUtil.getSizeT(meta, series),
1008                messageSuffix);
1009    }
1010
1011    // /**
1012    // * Returns the best resolution to use from the given metadata information and series index.<br>
1013    // * If the image is too large to be displayed at full resolution (XY plane size > 2^31) or if
1014    // we don't have enough
1015    // * memory to store the whole image (depending wanted constraint) then a sub resolution index
1016    // is returned.<br>
1017    // * A return value of <code>0</code> means full resolution of the original image while value
1018    // <code>1</code>
1019    // * correspond to the resolution / 2.<br>
1020    // * Formula: <code>resolution / 2^value</code><br>
1021    // *
1022    // * @param meta
1023    // * metadata of the image
1024    // * @param series
1025    // * series index
1026    // * @param showMessage
1027    // * show announce frame or message in the output log when one size constraint is meet and
1028    // induce resolution
1029    // * decrease
1030    // */
1031    // public static int getBestResolution(OMEXMLMetadataImpl meta, int series, boolean showMessage)
1032    // {
1033    // // easy trick to disable message display when needed
1034    // boolean warningDisplayed = !showMessage;
1035    // // default resolution to open (full resolution)
1036    // int resolution = 0;
1037    // // size of XY plane
1038    // long sizeXY = (long) MetaDataUtil.getSizeX(meta, series) * (long) MetaDataUtil.getSizeY(meta,
1039    // series);
1040    //
1041    // // we can't handle that plane size
1042    // if (sizeXY > Integer.MAX_VALUE)
1043    // {
1044    // if (!warningDisplayed)
1045    // {
1046    // // notify we can't open that image at full resolution
1047    // if (!Icy.getMainInterface().isHeadLess())
1048    // new AnnounceFrame("XY plane size is >= 2^31, try to open sub resolution of the image...",
1049    // 10);
1050    // else
1051    // System.out.println("XY plane size is >= 2^31, try to open sub resolution of the image...");
1052    //
1053    // warningDisplayed = true;
1054    // }
1055    //
1056    // // reduce resolution until XY plane size is acceptable
1057    // do
1058    // {
1059    // resolution++;
1060    // sizeXY /= 4;
1061    // }
1062    // while (sizeXY > Integer.MAX_VALUE);
1063    // }
1064    //
1065    // // get free memory
1066    // long freeInByte = SystemUtil.getJavaFreeMemory() - (16 * 1024 * 1024);
1067    // // check that we have enough memory for the whole image and the ARGB image used for display
1068    // (sizeXY * 4)
1069    // long sizeInByte = MetaDataUtil.getDataSize(meta, series, resolution) + (sizeXY * 4);
1070    //
1071    // // not enough memory to store the whole image ?
1072    // if (sizeInByte > freeInByte)
1073    // {
1074    // // try to release some memory
1075    // System.gc();
1076    // // get updated free memory
1077    // freeInByte = SystemUtil.getJavaFreeMemory() - (16 * 1024 * 1024);
1078    // }
1079    //
1080    // // still not enough memory ?
1081    // if (sizeInByte > freeInByte)
1082    // {
1083    // if (!warningDisplayed)
1084    // {
1085    // // display an information message that we can only load a sub resolution of the image
1086    // if (!Icy.getMainInterface().isHeadLess())
1087    // new AnnounceFrame(
1088    // "Not enough memory to open full resolution of the image, try to open sub resolution...", 10);
1089    // else
1090    // System.out
1091    // .println("Not enough memory to open full resolution of the image, try to open sub
1092    // resolution...");
1093    //
1094    // warningDisplayed = true;
1095    // }
1096    //
1097    // // reduce image resolution so the whole image fit in about 70% of available memory (safe)
1098    // freeInByte = (int) (freeInByte * 0.7d);
1099    // do
1100    // {
1101    // resolution++;
1102    // sizeInByte /= 4;
1103    // }
1104    // while (sizeInByte > freeInByte);
1105    // }
1106    //
1107    // return resolution;
1108    // }
1109
1110    /**
1111     * @deprecated Use {@link #getSequenceFileImporters(String)} instead.
1112     */
1113    @SuppressWarnings("resource")
1114    @Deprecated
1115    public static IFormatReader getReader(String path) throws FormatException, IOException
1116    {
1117        return new ImageReader().getReader(path);
1118    }
1119
1120    /**
1121     * Loads and returns metadata of the specified image file with given importer.<br>
1122     * It can returns <code>null</code> if the specified file is not a valid or supported) image
1123     * file.
1124     */
1125    public static OMEXMLMetadata getOMEXMLMetaData(SequenceFileImporter importer, String path)
1126            throws UnsupportedFormatException, IOException
1127    {
1128        if (importer.open(path, 0))
1129        {
1130            try
1131            {
1132                return importer.getOMEXMLMetaData();
1133            }
1134            finally
1135            {
1136                importer.close();
1137            }
1138        }
1139
1140        return null;
1141    }
1142
1143    /**
1144     * Loads and returns metadata of the specified image file.<br>
1145     * It can returns <code>null</code> if the specified file is not a valid or supported) image
1146     * file.
1147     */
1148    public static OMEXMLMetadata getOMEXMLMetaData(String path) throws UnsupportedFormatException, IOException
1149    {
1150        OMEXMLMetadata result;
1151        UnsupportedFormatException lastError = null;
1152
1153        for (SequenceFileImporter importer : getSequenceFileImporters(path))
1154        {
1155            try
1156            {
1157                result = getOMEXMLMetaData(importer, path);
1158
1159                if (result != null)
1160                    return result;
1161            }
1162            catch (UnsupportedFormatException e)
1163            {
1164                lastError = e;
1165            }
1166        }
1167
1168        throw new UnsupportedFormatException("Image file '" + path + "' is not supported :\n", lastError);
1169    }
1170
1171    /**
1172     * @deprecated Use {@link #getOMEXMLMetaData(SequenceFileImporter, String)} instead.
1173     */
1174    @Deprecated
1175    public static OMEXMLMetadataImpl getMetaData(SequenceFileImporter importer, String path)
1176            throws UnsupportedFormatException, IOException
1177    {
1178        if (importer.open(path, 0))
1179        {
1180            try
1181            {
1182                return importer.getMetaData();
1183            }
1184            finally
1185            {
1186                importer.close();
1187            }
1188        }
1189
1190        return null;
1191    }
1192
1193    /**
1194     * @throws IOException
1195     * @deprecated Use {@link #getOMEXMLMetaData(String)} instead
1196     */
1197    @Deprecated
1198    public static OMEXMLMetadataImpl getMetaData(String path) throws UnsupportedFormatException, IOException
1199    {
1200        OMEXMLMetadataImpl result;
1201        UnsupportedFormatException lastError = null;
1202
1203        for (SequenceFileImporter importer : getSequenceFileImporters(path))
1204        {
1205            try
1206            {
1207                result = getMetaData(importer, path);
1208
1209                if (result != null)
1210                    return result;
1211            }
1212            catch (UnsupportedFormatException e)
1213            {
1214                lastError = e;
1215            }
1216        }
1217
1218        throw new UnsupportedFormatException("Image file '" + path + "' is not supported :\n", lastError);
1219    }
1220
1221    /**
1222     * @deprecated Use {@link #getOMEXMLMetaData(String)} instead.
1223     */
1224    @Deprecated
1225    public static OMEXMLMetadataImpl getMetaData(File file) throws UnsupportedFormatException, IOException
1226    {
1227        return getMetaData(file.getAbsolutePath());
1228    }
1229
1230    /**
1231     * @deprecated Use {@link #getOMEXMLMetaData(String)} instead.
1232     */
1233    @Deprecated
1234    protected static OMEXMLMetadataImpl getMetaData(IFormatReader reader, String path)
1235            throws FormatException, IOException
1236    {
1237        // prepare meta data store structure
1238        reader.setMetadataStore((MetadataStore) OMEUtil.createOMEXMLMetadata());
1239        // load file with LOCI library
1240        reader.setId(path);
1241
1242        return (OMEXMLMetadataImpl) reader.getMetadataStore();
1243    }
1244
1245    // /**
1246    // * Use the given importer to load and return metadata of the specified image file.
1247    // *
1248    // * @throws UnsupportedFormatException
1249    // * @throws IOException
1250    // */
1251    // public static OMEXMLMetadataImpl getMetaData(SequenceFileImporter importer, File file)
1252    // throws UnsupportedFormatException, IOException
1253    // {
1254    // // load current file and add to results
1255    // return importer.getMetaData(file);
1256    // }
1257
1258    /**
1259     * Returns a thumbnail of the specified image file path.<br>
1260     * It can return <code>null</code> if the specified file is not a valid or supported image file.
1261     * 
1262     * @param importer
1263     *        Importer used to open and load the thumbnail from the image file.
1264     * @param path
1265     *        image file path.
1266     * @param series
1267     *        Series index we want to retrieve thumbnail from (for multi series image).<br>
1268     *        Set to 0 if unsure.
1269     */
1270    public static IcyBufferedImage loadThumbnail(SequenceFileImporter importer, String path, int series)
1271            throws UnsupportedFormatException, IOException
1272    {
1273        if (importer.open(path, 0))
1274        {
1275            try
1276            {
1277                return importer.getThumbnail(series);
1278            }
1279            finally
1280            {
1281                importer.close();
1282            }
1283        }
1284
1285        return null;
1286    }
1287
1288    /**
1289     * Returns a thumbnail of the specified image file path.
1290     * 
1291     * @param path
1292     *        image file path.
1293     * @param series
1294     *        Series index we want to retrieve thumbnail from (for multi series image).<br>
1295     *        Set to 0 if unsure.
1296     */
1297    public static IcyBufferedImage loadThumbnail(String path, int series) throws UnsupportedFormatException, IOException
1298    {
1299        IcyBufferedImage result;
1300        UnsupportedFormatException lastError = null;
1301
1302        for (SequenceFileImporter importer : getSequenceFileImporters(path))
1303        {
1304            try
1305            {
1306                result = loadThumbnail(importer, path, series);
1307
1308                if (result != null)
1309                    return result;
1310            }
1311            catch (UnsupportedFormatException e)
1312            {
1313                lastError = e;
1314            }
1315        }
1316
1317        throw new UnsupportedFormatException("Image file '" + path + "' is not supported :\n", lastError);
1318    }
1319
1320    /**
1321     * @deprecated Use {@link IcyBufferedImage#createFrom(IFormatReader, int, int)} instead.
1322     */
1323    @Deprecated
1324    public static IcyBufferedImage loadImage(IFormatReader reader, int z, int t) throws FormatException, IOException
1325    {
1326        // return an icy image
1327        return IcyBufferedImage.createFrom(reader, z, t);
1328    }
1329
1330    /**
1331     * @deprecated Use {@link IcyBufferedImage#createFrom(IFormatReader, int, int)} with Z and T
1332     *             parameters set to 0.
1333     */
1334    @Deprecated
1335    public static IcyBufferedImage loadImage(IFormatReader reader) throws FormatException, IOException
1336    {
1337        // return an icy image
1338        return IcyBufferedImage.createFrom(reader, 0, 0);
1339    }
1340
1341    /**
1342     * @deprecated Use {@link #loadImage(String, int, int)} instead.
1343     */
1344    @Deprecated
1345    public static IcyBufferedImage loadImage(File file, int z, int t) throws FormatException, IOException
1346    {
1347        return loadImage(file.getAbsolutePath(), z, t);
1348    }
1349
1350    /**
1351     * @deprecated Use {@link #loadImage(String)} instead.
1352     */
1353    @Deprecated
1354    public static IcyBufferedImage loadImage(File file) throws UnsupportedFormatException, IOException
1355    {
1356        return loadImage(file.getAbsolutePath());
1357    }
1358
1359    /**
1360     * @deprecated Use {@link #loadImage(String, int, int, int)} instead.
1361     */
1362    @Deprecated
1363    public static IcyBufferedImage loadImage(String path, int z, int t) throws FormatException, IOException
1364    {
1365        final IFormatReader reader = getReader(path);
1366
1367        // disable file grouping
1368        reader.setGroupFiles(false);
1369        // set file path
1370        reader.setId(path);
1371        try
1372        {
1373            // return an icy image
1374            return IcyBufferedImage.createFrom(reader, z, t);
1375        }
1376        finally
1377        {
1378            // close reader
1379            reader.close();
1380        }
1381    }
1382
1383    /**
1384     * Load and return the image at given position from the specified file path.<br>
1385     * For lower image level access, you can use importer methods.
1386     * 
1387     * @param importer
1388     *        Importer used to open and load the image file.
1389     * @param path
1390     *        image file path.
1391     * @param series
1392     *        Series index we want to retrieve image from (for multi series image).<br>
1393     *        Set to 0 if unsure (default).
1394     * @param z
1395     *        Z position of the image to open.
1396     * @param t
1397     *        T position of the image to open.
1398     * @throws IOException
1399     * @throws UnsupportedFormatException
1400     */
1401    public static IcyBufferedImage loadImage(SequenceFileImporter importer, String path, int series, int z, int t)
1402            throws UnsupportedFormatException, IOException
1403    {
1404        if ((importer == null) || !importer.open(path, 0))
1405            throw new UnsupportedFormatException("Image file '" + path + "' is not supported !");
1406
1407        try
1408        {
1409            return importer.getImage(series, z, t);
1410        }
1411        finally
1412        {
1413            importer.close();
1414        }
1415    }
1416
1417    /**
1418     * Load and return the image at given position from the specified file path.<br>
1419     * For lower image level access, you can use {@link #getSequenceFileImporter(String, boolean)}
1420     * method and
1421     * directly work through the returned {@link ImageProvider} interface.
1422     * 
1423     * @param path
1424     *        image file path.
1425     * @param series
1426     *        Series index we want to retrieve image from (for multi series image).<br>
1427     *        Set to 0 if unsure (default).
1428     * @param z
1429     *        Z position of the image to open.
1430     * @param t
1431     *        T position of the image to open.
1432     * @throws IOException
1433     * @throws UnsupportedFormatException
1434     */
1435    public static IcyBufferedImage loadImage(String path, int series, int z, int t)
1436            throws UnsupportedFormatException, IOException
1437    {
1438        return loadImage(getSequenceFileImporter(path, true), path, series, z, t);
1439    }
1440
1441    /**
1442     * Load and return a single image from the specified file path.<br>
1443     * If the specified file contains severals image the first image is returned.
1444     */
1445    public static IcyBufferedImage loadImage(String path) throws UnsupportedFormatException, IOException
1446    {
1447        return loadImage(path, 0, 0, 0);
1448    }
1449
1450    /**
1451     * @deprecated Use {@link #loadSequences(File[], int, boolean, boolean, boolean)} instead.
1452     */
1453    @Deprecated
1454    public static Sequence[] loadSequences(File[] files, int[] series, boolean separate, boolean autoOrder,
1455            boolean showProgress)
1456    {
1457        final List<Sequence> result = new ArrayList<Sequence>();
1458        final List<String> paths = FileUtil.toPaths(CollectionUtil.asList(files));
1459
1460        if (series == null)
1461            result.addAll(loadSequences(paths, -1, separate, autoOrder, false, showProgress));
1462        else
1463        {
1464            for (int s : series)
1465                result.addAll(loadSequences(paths, s, separate, autoOrder, false, showProgress));
1466        }
1467
1468        return result.toArray(new Sequence[result.size()]);
1469    }
1470
1471    /**
1472     * @deprecated Use {@link #loadSequences(File[], int[], boolean, boolean, boolean)} instead.
1473     */
1474    @Deprecated
1475    public static List<Sequence> loadSequences(List<File> files, List<Integer> series, boolean separate,
1476            boolean autoOrder, boolean showProgress)
1477    {
1478        final int[] seriesArray;
1479
1480        if (series != null)
1481        {
1482            seriesArray = new int[series.size()];
1483
1484            for (int i = 0; i < seriesArray.length; i++)
1485                seriesArray[i] = series.get(i).intValue();
1486        }
1487        else
1488        {
1489            seriesArray = new int[1];
1490            seriesArray[0] = 0;
1491        }
1492
1493        return Arrays.asList(
1494                loadSequences(files.toArray(new File[files.size()]), seriesArray, separate, autoOrder, showProgress));
1495    }
1496
1497    /**
1498     * @deprecated Use {@link #loadSequences(File[], int[], boolean, boolean, boolean)} instead.
1499     */
1500    @Deprecated
1501    public static List<Sequence> loadSequences(List<File> files, List<Integer> series, boolean separate,
1502            boolean showProgress)
1503    {
1504        return loadSequences(files, series, separate, true, showProgress);
1505    }
1506
1507    /**
1508     * @deprecated Use {@link #loadSequences(File[], int[], boolean, boolean, boolean)} instead.
1509     */
1510    @Deprecated
1511    public static List<Sequence> loadSequences(List<File> files, List<Integer> series, boolean separate)
1512    {
1513        return loadSequences(files, series, separate, true, true);
1514    }
1515
1516    /**
1517     * @deprecated Use {@link #loadSequences(File[], int[], boolean, boolean, boolean)} instead.
1518     */
1519    @Deprecated
1520    public static List<Sequence> loadSequences(List<File> files, List<Integer> series)
1521    {
1522        return loadSequences(files, series, false, true, true);
1523    }
1524
1525    /**
1526     * @deprecated Use {@link #loadSequences(File[], int[], boolean, boolean, boolean)} instead.
1527     */
1528    @Deprecated
1529    public static List<Sequence> loadSequences(List<File> files, boolean separate, boolean showProgress)
1530    {
1531        return loadSequences(files, null, separate, true, showProgress);
1532    }
1533
1534    /**
1535     * @deprecated Use {@link #loadSequences(File[], int[], boolean, boolean, boolean)} instead.
1536     */
1537    @Deprecated
1538    public static List<Sequence> loadSequences(List<File> files, boolean separate)
1539    {
1540        return loadSequences(files, null, separate, true, true);
1541    }
1542
1543    /**
1544     * @deprecated Use {@link #loadSequences(File[], int[], boolean, boolean, boolean)} instead.
1545     */
1546    @Deprecated
1547    public static List<Sequence> loadSequences(List<File> files, boolean separate, boolean display, boolean addToRecent)
1548    {
1549        return loadSequences(files, null, separate, true, true);
1550    }
1551
1552    /**
1553     * @deprecated Use {@link #loadSequence(File, int, boolean)} instead.
1554     */
1555    @Deprecated
1556    public static Sequence[] loadSequences(File file, int[] series, boolean showProgress)
1557    {
1558        return loadSequences(new File[] {file}, series, false, true, showProgress);
1559    }
1560
1561    /**
1562     * @deprecated Use {@link #loadSequences(File, int[], boolean)} instead.
1563     */
1564    @Deprecated
1565    public static List<Sequence> loadSequences(File file, List<Integer> series, boolean showProgress)
1566    {
1567        final int[] seriesArray;
1568
1569        if (series != null)
1570        {
1571            seriesArray = new int[series.size()];
1572
1573            for (int i = 0; i < seriesArray.length; i++)
1574                seriesArray[i] = series.get(i).intValue();
1575        }
1576        else
1577        {
1578            seriesArray = new int[1];
1579            seriesArray[0] = 0;
1580        }
1581
1582        return Arrays.asList(loadSequences(new File[] {file}, seriesArray, false, true, showProgress));
1583    }
1584
1585    /**
1586     * @deprecated Use {@link #loadSequences(File, int[], boolean)} instead.
1587     */
1588    @Deprecated
1589    public static List<Sequence> loadSequences(File file, List<Integer> series)
1590    {
1591        return loadSequences(file, series, true);
1592    }
1593
1594    /**
1595     * @deprecated Use {@link #loadSequences(File, int[], boolean)} instead.
1596     */
1597    @Deprecated
1598    public static List<Sequence> loadSequences(File file, List<Integer> series, boolean display, boolean addToRecent)
1599    {
1600        return loadSequences(file, series, true);
1601    }
1602
1603    /**
1604     * @deprecated Use {@link #loadSequence(File, int, boolean)} instead.
1605     */
1606    @Deprecated
1607    public static Sequence loadSequence(File file, boolean showProgress)
1608    {
1609        return loadSequence(new File[] {file}, -1, showProgress);
1610    }
1611
1612    /**
1613     * @deprecated Use {@link #loadSequence(File, int, boolean)} instead.
1614     */
1615    @Deprecated
1616    public static Sequence loadSequence(File file)
1617    {
1618        return loadSequence(new File[] {file}, -1, true);
1619    }
1620
1621    /**
1622     * @deprecated Use {@link #loadSequences(List, int, boolean, boolean, boolean, boolean)}
1623     *             instead.
1624     */
1625    @Deprecated
1626    public static Sequence[] loadSequences(File[] files, int series, boolean separate, boolean autoOrder,
1627            boolean showProgress)
1628    {
1629        final List<Sequence> result = loadSequences(FileUtil.toPaths(CollectionUtil.asList(files)), series, separate,
1630                autoOrder, false, showProgress);
1631        return result.toArray(new Sequence[result.size()]);
1632    }
1633
1634    /**
1635     * Load a sequence from the specified list of file and returns it.<br>
1636     * As the function can take sometime you should not call it from the AWT EDT.<br>
1637     * The function can return null if no sequence can be loaded from the specified files.
1638     * 
1639     * @param files
1640     *        List of image file to load.
1641     * @param series
1642     *        Series index to load (for multi series sequence), set to 0 if unsure (default).
1643     * @param showProgress
1644     *        Show progression of loading process.
1645     */
1646    @Deprecated
1647    public static Sequence loadSequence(File[] files, int series, boolean showProgress)
1648    {
1649        final Sequence[] result = loadSequences(files, series, false, true, showProgress);
1650
1651        if (result.length > 0)
1652            return result[0];
1653
1654        return null;
1655    }
1656
1657    /**
1658     * @deprecated Use {@link #loadSequence(String, int, boolean)} instead.
1659     */
1660    @Deprecated
1661    public static Sequence loadSequence(File file, int series, boolean showProgress)
1662    {
1663        return loadSequence(new File[] {file}, series, showProgress);
1664    }
1665
1666    /**
1667     * Load a list of sequence from the specified list of file with the given {@link SequenceFileImporter} and returns them.<br>
1668     * As the function can take sometime you should not call it from the AWT EDT.<br>
1669     * The method returns an empty array if an error occurred or if no file could be opened (not supported).<br>
1670     * If the user cancelled the action (series selection dialog) then it returns <code>null</code>.
1671     * 
1672     * @param importer
1673     *        Importer used to open and load image files.<br>
1674     *        If set to <code>null</code> the loader will search for a compatible importer and if
1675     *        several importers match the user will have to select the appropriate one from a
1676     *        selection dialog.
1677     * @param paths
1678     *        List of image file to load.
1679     * @param series
1680     *        Series index to load (for multi series sequence), set to 0 if unsure (default).<br>
1681     *        -1 is a special value so it gives a chance to the user<br>
1682     *        to select the series to open from a series selector dialog.
1683     * @param forceVolatile
1684     *        If set to <code>true</code> then image data is forced to volatile (see {@link IcyBufferedImage#isVolatile()}).<br>
1685     *        Note that if you don't have enough memory to load the whole Sequence into memory then image data are always made volatile.
1686     * @param separate
1687     *        Force image to be loaded in separate sequence.
1688     * @param autoOrder
1689     *        Try to order image in sequence from their filename
1690     * @param addToRecent
1691     *        If set to true the files list will be traced in recent opened sequence.
1692     * @param showProgress
1693     *        Show progression of loading process.
1694     */
1695    @SuppressWarnings("resource")
1696    public static List<Sequence> loadSequences(SequenceFileImporter importer, List<String> paths, int series,
1697            boolean forceVolatile, boolean separate, boolean autoOrder, boolean addToRecent, boolean showProgress)
1698    {
1699        final List<Sequence> result = new ArrayList<Sequence>();
1700
1701        // detect if this is a complete folder load
1702        final boolean directory = (paths.size() == 1) && new File(paths.get(0)).isDirectory();
1703        // explode path list
1704        final List<String> singlePaths = cleanNonImageFile(explode(paths));
1705        final boolean adjSeparate = (singlePaths.size() <= 1) || separate;
1706        // get the sequence importers first
1707        final Map<SequenceFileImporter, List<String>> sequenceFileImporters = getSequenceFileImporters(importer,
1708                singlePaths, !adjSeparate, false);
1709
1710        for (Entry<SequenceFileImporter, List<String>> entry : sequenceFileImporters.entrySet())
1711        {
1712            final SequenceFileImporter imp = entry.getKey();
1713            final List<String> currPaths = entry.getValue();
1714            final boolean dir = directory && (sequenceFileImporters.size() == 1)
1715                    && (currPaths.size() == singlePaths.size());
1716
1717            // load sequence
1718            result.addAll(loadSequences(imp, currPaths, series, forceVolatile, adjSeparate, autoOrder, dir, addToRecent,
1719                    showProgress));
1720
1721            // remove loaded files
1722            singlePaths.removeAll(currPaths);
1723        }
1724
1725        // remove remaining XML persistence files...
1726        for (int i = singlePaths.size() - 1; i >= 0; i--)
1727            if (SequencePersistent.isValidXMLPersitence(singlePaths.get(i)))
1728                singlePaths.remove(i);
1729
1730        // remaining files ?
1731        if (singlePaths.size() > 0)
1732        {
1733            // get first found importer for remaining files
1734            final Map<SequenceFileImporter, List<String>> importers = getSequenceFileImporters(singlePaths, true);
1735
1736            // user canceled action for these paths so we remove them
1737            for (List<String> values : importers.values())
1738                singlePaths.removeAll(values);
1739
1740            if (singlePaths.size() > 0)
1741            {
1742                // just log in console
1743                System.err.println("No compatible importer found for the following files:");
1744                for (String path : singlePaths)
1745                    System.err.println(path);
1746                System.err.println();
1747            }
1748        }
1749
1750        // return sequences
1751        return result;
1752    }
1753
1754    /**
1755     * Load a list of sequence from the specified list of file with the given {@link SequenceFileImporter} and returns them.<br>
1756     * As the function can take sometime you should not call it from the AWT EDT.<br>
1757     * The method returns an empty array if an error occurred or if no file could be opened (not supported).<br>
1758     * If the user cancelled the action (series selection dialog) then it returns <code>null</code>.
1759     * 
1760     * @param importer
1761     *        Importer used to open and load image files.<br>
1762     *        If set to <code>null</code> the loader will search for a compatible importer and if
1763     *        several importers match the user will have to select the appropriate one from a
1764     *        selection dialog.
1765     * @param paths
1766     *        List of image file to load.
1767     * @param series
1768     *        Series index to load (for multi series sequence), set to 0 if unsure (default).<br>
1769     *        -1 is a special value so it gives a chance to the user<br>
1770     *        to select the series to open from a series selector dialog.
1771     * @param separate
1772     *        Force image to be loaded in separate sequence.
1773     * @param autoOrder
1774     *        Try to order image in sequence from their filename
1775     * @param addToRecent
1776     *        If set to true the files list will be traced in recent opened sequence.
1777     * @param showProgress
1778     *        Show progression of loading process.
1779     */
1780    @SuppressWarnings("resource")
1781    public static List<Sequence> loadSequences(SequenceFileImporter importer, List<String> paths, int series,
1782            boolean separate, boolean autoOrder, boolean addToRecent, boolean showProgress)
1783    {
1784        return loadSequences(importer, paths, series, false, separate, autoOrder, addToRecent, showProgress);
1785    }
1786
1787    /**
1788     * Loads a list of sequence from the specified list of file and returns them.<br>
1789     * As the function can take sometime you should not call it from the AWT EDT.<br>
1790     * The method returns an empty array if an error occurred or if no file could not be opened (not
1791     * supported).<br>
1792     * If several importers match to open a file the user will have to select the appropriate one
1793     * from a selection dialog.
1794     * 
1795     * @param paths
1796     *        List of image file to load.
1797     * @param series
1798     *        Series index to load (for multi series sequence), set to 0 if unsure (default).<br>
1799     *        -1 is a special value so it gives a chance to the user<br>
1800     *        to select the series to open from a series selector dialog.
1801     * @param separate
1802     *        Force image to be loaded in separate sequence.
1803     * @param autoOrder
1804     *        Try to order image in sequence from their filename
1805     * @param addToRecent
1806     *        If set to true the files list will be traced in recent opened sequence.
1807     * @param showProgress
1808     *        Show progression of loading process.
1809     */
1810    public static List<Sequence> loadSequences(List<String> paths, int series, boolean separate, boolean autoOrder,
1811            boolean addToRecent, boolean showProgress)
1812    {
1813        return loadSequences(null, paths, series, separate, autoOrder, addToRecent, showProgress);
1814    }
1815
1816    /**
1817     * Load a sequence from the specified list of file and returns it.<br>
1818     * The function try to guess image ordering from file name and metadata.<br>
1819     * As the function can take sometime you should not call it from the AWT EDT.<br>
1820     * The function can return null if no sequence can be loaded from the specified files.
1821     * 
1822     * @param importer
1823     *        Importer used to load the image file (can be null for automatic selection).
1824     * @param paths
1825     *        List of image file to load.
1826     * @param addToRecent
1827     *        If set to true the files list will be traced in recent opened sequence.
1828     * @param showProgress
1829     *        Show progression of loading process.
1830     * @see #getSequenceFileImporter(String, boolean)
1831     */
1832    public static Sequence loadSequence(SequenceFileImporter importer, List<String> paths, boolean addToRecent,
1833            boolean showProgress)
1834    {
1835        final ApplicationMenu mainMenu;
1836        final FileFrame loadingFrame;
1837        Sequence result = null;
1838
1839        if (addToRecent)
1840            mainMenu = Icy.getMainInterface().getApplicationMenu();
1841        else
1842            mainMenu = null;
1843        if (showProgress && !Icy.getMainInterface().isHeadLess())
1844            loadingFrame = new FileFrame("Loading", null);
1845        else
1846            loadingFrame = null;
1847
1848        try
1849        {
1850            // we want only one group
1851            final SequenceFileGroup group = SequenceFileSticher.groupFiles(importer, cleanNonImageFile(paths),
1852                    showProgress, loadingFrame);
1853            // do group loading
1854            result = internalLoadGroup(group, false, false, mainMenu, loadingFrame);
1855            // load sequence XML data
1856            if (GeneralPreferences.getSequencePersistence())
1857                result.loadXMLData();
1858        }
1859        catch (Throwable t)
1860        {
1861            // just show the error
1862            IcyExceptionHandler.showErrorMessage(t, true);
1863
1864            if (loadingFrame != null)
1865                new FailedAnnounceFrame((t instanceof OutOfMemoryError) ? t.getMessage()
1866                        : "Failed to open file(s), see the console output for more details.");
1867        }
1868        finally
1869        {
1870            if (loadingFrame != null)
1871                loadingFrame.close();
1872        }
1873
1874        return result;
1875    }
1876
1877    /**
1878     * Load a sequence from the specified list of file and returns it.<br>
1879     * As the function can take sometime you should not call it from the AWT EDT.<br>
1880     * The function can return null if no sequence can be loaded from the specified files.
1881     * 
1882     * @param importer
1883     *        Importer used to load the image file (can be null for automatic selection).
1884     * @param paths
1885     *        List of image file to load.
1886     * @param showProgress
1887     *        Show progression of loading process.
1888     * @see #getSequenceFileImporter(String, boolean)
1889     */
1890    public static Sequence loadSequence(SequenceFileImporter importer, List<String> paths, boolean showProgress)
1891    {
1892        return loadSequence(importer, paths, false, showProgress);
1893    }
1894
1895    /**
1896     * @deprecated Use {@link #loadSequence(SequenceFileImporter, List, boolean, boolean)} instead
1897     */
1898    @Deprecated
1899    public static Sequence loadSequence(List<?> files, boolean display, boolean addToRecent)
1900    {
1901        return loadSequence(files, true);
1902    }
1903
1904    /**
1905     * Load a sequence from the specified list of file and returns it.<br>
1906     * As the function can take sometime you should not call it from the AWT EDT.<br>
1907     * The function can return null if no sequence can be loaded from the specified files.<br>
1908     * If several importers match to open a file the user will have to select the appropriate one
1909     * from a selection dialog.
1910     * 
1911     * @param files
1912     *        List of image file to load (can be String or File object).
1913     * @param showProgress
1914     *        Show progression of loading process.
1915     */
1916    @SuppressWarnings("unchecked")
1917    public static Sequence loadSequence(List<?> files, boolean showProgress)
1918    {
1919        if (files.size() == 0)
1920            return null;
1921
1922        final List<String> paths;
1923
1924        if (files.get(0) instanceof File)
1925            paths = FileUtil.toPaths((List<File>) files);
1926        else
1927            paths = (List<String>) files;
1928
1929        return loadSequence(null, paths, showProgress);
1930    }
1931
1932    /**
1933     * Load a sequence from the specified list of file and returns it.<br>
1934     * As the function can take sometime you should not call it from the AWT EDT.<br>
1935     * The function can return null if no sequence can be loaded from the specified files.<br>
1936     * If several importers match to open a file the user will have to select the appropriate one
1937     * from a selection dialog.
1938     * 
1939     * @param files
1940     *        List of image file to load (can be String or File object).
1941     */
1942    public static Sequence loadSequence(List<?> files)
1943    {
1944        return loadSequence(files, true);
1945    }
1946
1947    /**
1948     * Loads the specified image file and return it as a Sequence, it can return <code>null</code>
1949     * if an error occured.<br>
1950     * As this method can take sometime, you should not call it from the EDT.
1951     * 
1952     * @param importer
1953     *        Importer used to load the image file.<br>
1954     *        If set to <code>null</code> the loader will search for a compatible importer and if
1955     *        several importers
1956     *        match the user will have to select the appropriate one from a selection dialog if
1957     *        <code>showProgress</code> parameter is set to <code>true</code> otherwise the first
1958     *        compatible importer will be automatically used.
1959     * @param path
1960     *        Image file to load.
1961     * @param series
1962     *        Series index to load (for multi series sequence), set to 0 if unsure (default).<br>
1963     *        -1 is a special value so it gives a chance to the user to select series to open from a
1964     *        series selector dialog.
1965     * @param addToRecent
1966     *        If set to true the path will be traced in recent opened sequence.
1967     * @param showProgress
1968     *        Show progression of loading process.
1969     */
1970    public static Sequence loadSequence(SequenceFileImporter importer, String path, int series, boolean addToRecent,
1971            boolean showProgress)
1972    {
1973        return loadSequence(importer, path, series, 0, null, -1, -1, -1, -1, -1, addToRecent, showProgress);
1974    }
1975
1976    /**
1977     * Load a sequence from the specified file.<br>
1978     * As the function can take sometime you should not call it from the AWT EDT.
1979     * 
1980     * @param importer
1981     *        Importer used to load the image file.<br>
1982     *        If set to <code>null</code> the loader will search for a compatible importer and if
1983     *        several importers
1984     *        match the user will have to select the appropriate one from a selection dialog if
1985     *        <code>showProgress</code> parameter is set to <code>true</code> otherwise the first
1986     *        compatible importer will be automatically used.
1987     * @param path
1988     *        Image file to load.
1989     * @param series
1990     *        Series index to load (for multi series sequence), set to 0 if unsure (default).
1991     * @param showProgress
1992     *        Show progression of loading process.
1993     */
1994    public static Sequence loadSequence(SequenceFileImporter importer, String path, int series, boolean showProgress)
1995    {
1996        return loadSequence(importer, path, series, false, showProgress);
1997    }
1998
1999    /**
2000     * Load a sequence from the specified file.<br>
2001     * As the function can take sometime you should not call it from the AWT EDT.<br>
2002     * If several importers match to open the file the user will have to select the appropriate one
2003     * from a selection dialog if <code>showProgress</code> parameter is set to <code>true</code>
2004     * otherwise the first
2005     * compatible importer is automatically used.
2006     * 
2007     * @param path
2008     *        Image file to load.
2009     * @param series
2010     *        Series index to load (for multi series sequence), set to 0 if unsure (default).
2011     * @param showProgress
2012     *        Show progression of loading process.
2013     */
2014    public static Sequence loadSequence(String path, int series, boolean showProgress)
2015    {
2016        return loadSequence(null, path, series, showProgress);
2017    }
2018
2019    /**
2020     * @deprecated Use {@link #load(List, boolean, boolean, boolean)} instead.
2021     */
2022    @Deprecated
2023    public static void load(List<File> files)
2024    {
2025        load(files.toArray(new File[files.size()]), false, true, true);
2026    }
2027
2028    /**
2029     * @deprecated Use {@link #load(List, boolean, boolean, boolean)} instead.
2030     */
2031    @Deprecated
2032    public static void load(List<File> files, boolean separate)
2033    {
2034        load(files.toArray(new File[files.size()]), separate, true, true);
2035    }
2036
2037    /**
2038     * @deprecated Use {@link #load(List, boolean, boolean, boolean)} instead.
2039     */
2040    @Deprecated
2041    public static void load(List<File> files, boolean separate, boolean showProgress)
2042    {
2043        load(files.toArray(new File[files.size()]), separate, true, showProgress);
2044    }
2045
2046    // /**
2047    // * @deprecated Use {@link #load(File[], boolean, boolean, boolean)} instead.
2048    // */
2049    // @Deprecated
2050    // public static void load(List<File> files, boolean separate, boolean autoOrder, boolean
2051    // showProgress)
2052    // {
2053    // load(files.toArray(new File[files.size()]), separate, autoOrder, showProgress);
2054    // }
2055
2056    /**
2057     * @deprecated Use {@link #load(String, boolean)} instead.
2058     */
2059    @Deprecated
2060    public static void load(File file)
2061    {
2062        load(new File[] {file}, false, false, true);
2063    }
2064
2065    /**
2066     * @deprecated Use {@link #load(List, boolean, boolean, boolean)} instead.
2067     */
2068    @Deprecated
2069    public static void load(final File[] files, final boolean separate, final boolean autoOrder,
2070            final boolean showProgress)
2071    {
2072        // asynchronous call
2073        ThreadUtil.bgRun(new Runnable()
2074        {
2075            @Override
2076            public void run()
2077            {
2078                // load sequence
2079                final Sequence[] sequences = loadSequences(files, -1, separate, autoOrder, showProgress);
2080                // and display them
2081                for (Sequence seq : sequences)
2082                    Icy.getMainInterface().addSequence(seq);
2083            }
2084        });
2085    }
2086
2087    /**
2088     * @deprecated Use {@link #load(String, boolean)} instead.
2089     */
2090    @Deprecated
2091    public static void load(File file, boolean showProgress)
2092    {
2093        load(new File[] {file}, false, false, showProgress);
2094    }
2095
2096    /**
2097     * Load the specified files with the given {@link FileImporter}.<br>
2098     * The loading process is asynchronous.<br>
2099     * The FileImporter is responsible to make the loaded files available in the application.<br>
2100     * This method should be used only for non image file.
2101     * 
2102     * @param importer
2103     *        Importer used to open and load files.<br>
2104     *        If set to <code>null</code> the loader will search for a compatible importer and if
2105     *        several importers match the user will have to select the appropriate one from a
2106     *        selection dialog.
2107     * @param paths
2108     *        list of file to load
2109     * @param showProgress
2110     *        Show progression in loading process
2111     */
2112    public static void load(final FileImporter importer, final List<String> paths, final boolean showProgress)
2113    {
2114        // asynchronous call
2115        ThreadUtil.bgRun(new Runnable()
2116        {
2117            @Override
2118            public void run()
2119            {
2120                // explode path list
2121                final List<String> singlePaths = explode(paths);
2122
2123                if (singlePaths.size() > 0)
2124                {
2125                    // get the file importer now for remaining file
2126                    final Map<FileImporter, List<String>> fileImporters;
2127
2128                    // importer not defined --> find the appropriate importers
2129                    if (importer == null)
2130                        fileImporters = getFileImporters(singlePaths, false);
2131                    else
2132                    {
2133                        fileImporters = new HashMap<FileImporter, List<String>>(1);
2134                        fileImporters.put(importer, new ArrayList<String>(singlePaths));
2135                    }
2136
2137                    for (Entry<FileImporter, List<String>> entry : fileImporters.entrySet())
2138                    {
2139                        final FileImporter importer = entry.getKey();
2140                        final List<String> currPaths = entry.getValue();
2141
2142                        // load files
2143                        loadFiles(importer, paths, true, showProgress);
2144
2145                        // remove loaded files
2146                        singlePaths.removeAll(currPaths);
2147                    }
2148                }
2149
2150                // remaining files ?
2151                if (singlePaths.size() > 0)
2152                {
2153                    // get first found importer for remaining files
2154                    final Map<SequenceFileImporter, List<String>> importers = getSequenceFileImporters(singlePaths,
2155                            true);
2156
2157                    // user canceled action for these paths so we remove them
2158                    for (List<String> values : importers.values())
2159                        singlePaths.removeAll(values);
2160
2161                    if (singlePaths.size() > 0)
2162                    {
2163                        // just log in console
2164                        System.err.println("No compatible importer found for the following files:");
2165                        for (String path : singlePaths)
2166                            System.err.println(path);
2167                        System.err.println();
2168                    }
2169                }
2170            }
2171        });
2172    }
2173
2174    /**
2175     * Load the specified files with the given {@link FileImporter}.<br>
2176     * The FileImporter is responsible to make the loaded files available in the application.<br>
2177     * This method should be used only for non image file.
2178     * 
2179     * @param importer
2180     *        Importer used to open and load image files.
2181     * @param paths
2182     *        list of file to load
2183     * @param addToRecent
2184     *        If set to true the files list will be traced in recent opened files.
2185     * @param showProgress
2186     *        Show progression in loading process
2187     */
2188    static void loadFiles(FileImporter importer, List<String> paths, boolean addToRecent, boolean showProgress)
2189    {
2190        final ApplicationMenu mainMenu;
2191        final FileFrame loadingFrame;
2192
2193        if (addToRecent)
2194            mainMenu = Icy.getMainInterface().getApplicationMenu();
2195        else
2196            mainMenu = null;
2197        if (showProgress && !Icy.getMainInterface().isHeadLess())
2198        {
2199            loadingFrame = new FileFrame("Loading", null);
2200            loadingFrame.setLength(paths.size());
2201            loadingFrame.setPosition(0);
2202        }
2203        else
2204            loadingFrame = null;
2205
2206        try
2207        {
2208            // load each file in a separate sequence
2209            for (String path : paths)
2210            {
2211                if (loadingFrame != null)
2212                    loadingFrame.incPosition();
2213
2214                // load current file
2215                importer.load(path, loadingFrame);
2216
2217                // add as separate item to recent file list
2218                if (mainMenu != null)
2219                    mainMenu.addRecentLoadedFile(new File(FileUtil.getGenericPath(path)));
2220            }
2221        }
2222        catch (Throwable t)
2223        {
2224            // just show the error
2225            IcyExceptionHandler.showErrorMessage(t, true);
2226            if (loadingFrame != null)
2227                new FailedAnnounceFrame("Failed to open file(s), see the console output for more details.");
2228        }
2229        finally
2230        {
2231            if (loadingFrame != null)
2232                loadingFrame.close();
2233        }
2234    }
2235
2236    /**
2237     * Load the specified image file with the given {@link SequenceFileImporter}.<br>
2238     * The loading process is asynchronous.<br>
2239     * The resulting sequence is automatically displayed when the process complete.
2240     * 
2241     * @param importer
2242     *        Importer used to load the image file.<br>
2243     *        If set to <code>null</code> the loader will search for a compatible importer and if
2244     *        several importers match the user will have to select the appropriate one from a selection dialog if
2245     *        <code>showProgress</code> parameter is set to <code>true</code> otherwise the first
2246     *        compatible importer will be automatically used.
2247     * @param path
2248     *        image file to load
2249     * @param series
2250     *        Series index to load (for multi series sequence), set to 0 if unsure (default).<br>
2251     *        -1 is a special value so it gives a chance to the user to select series to open from a
2252     *        series selector dialog.
2253     * @param resolution
2254     *        Wanted resolution level for the image (use 0 if unsure), useful for large image<br>
2255     *        The retrieved image resolution is equal to
2256     *        <code>image.resolution / (2^resolution)</code><br>
2257     *        So for instance level 0 is the default/full image resolution while level 1 is base
2258     *        image
2259     *        resolution / 2 and so on...
2260     * @param region
2261     *        The 2D region of the image we want to retrieve.<br>
2262     *        If set to <code>null</code> then the whole XY plane of the image is returned.
2263     * @param minZ
2264     *        the minimum Z position of the image (slice) we want retrieve (inclusive).<br>
2265     *        Set to -1 to retrieve the whole stack.
2266     * @param maxZ
2267     *        the maximum Z position of the image (slice) we want retrieve (inclusive).<br>
2268     *        Set to -1 to retrieve the whole stack.
2269     * @param minT
2270     *        the minimum T position of the image (frame) we want retrieve (inclusive).<br>
2271     *        Set to -1 to retrieve the whole timelaps.
2272     * @param maxT
2273     *        the maximum T position of the image (frame) we want retrieve (inclusive).<br>
2274     *        Set to -1 to retrieve the whole timelaps.
2275     * @param channel
2276     *        C position of the image (channel) we want retrieve (-1 means all channel).
2277     * @param forceVolatile
2278     *        If set to <code>true</code> then image data is forced to volatile (see {@link IcyBufferedImage#isVolatile()}).<br>
2279     *        Note that if you don't have enough memory to load the whole Sequence into memory then image data are always made volatile.
2280     * @param separate
2281     *        Force image to be loaded in separate sequence if possible (disable stitching if any)
2282     * @param addToRecent
2283     *        If set to true the files list will be traced in recent opened sequence.
2284     * @param showProgress
2285     *        Show progression in loading process
2286     */
2287    public static void load(final SequenceFileImporter importer, final String path, final int series,
2288            final int resolution, final Rectangle region, final int minZ, final int maxZ, final int minT,
2289            final int maxT, final int channel, final boolean forceVolatile, final boolean separate,
2290            final boolean addToRecent, final boolean showProgress)
2291    {
2292        // asynchronous call
2293        ThreadUtil.bgRun(new Runnable()
2294        {
2295            @Override
2296            public void run()
2297            {
2298                // normal opening operation ?
2299                if ((resolution == 0) && (region == null) && (minZ <= 0) && (maxZ == -1) && (minT <= 0) && (maxT == -1)
2300                        && (channel == -1))
2301                {
2302                    // load sequence (we use this method to allow multi series opening)
2303                    final List<Sequence> sequences = loadSequences(
2304                            (importer == null) ? getSequenceFileImporter(path, !showProgress) : importer,
2305                            CollectionUtil.asList(path), series, forceVolatile, separate, false, false, addToRecent,
2306                            showProgress);
2307
2308                    // and display them
2309                    for (Sequence sequence : sequences)
2310                        if (sequence != null)
2311                            Icy.getMainInterface().addSequence(sequence);
2312                }
2313                else
2314                {
2315                    // load sequence
2316                    final Sequence sequence = loadSequence(importer, path, series, resolution, region, minZ, maxZ, minT,
2317                            maxT, channel, forceVolatile, addToRecent, showProgress);
2318
2319                    // and display it
2320                    if (sequence != null)
2321                        Icy.getMainInterface().addSequence(sequence);
2322                }
2323            }
2324        });
2325    }
2326
2327    /**
2328     * Load the specified image file with the given {@link SequenceFileImporter}.<br>
2329     * The loading process is asynchronous.<br>
2330     * The resulting sequence is automatically displayed when the process complete.
2331     * 
2332     * @param importer
2333     *        Importer used to load the image file.<br>
2334     *        If set to <code>null</code> the loader will search for a compatible importer and if
2335     *        several importers match the user will have to select the appropriate one from a selection dialog if
2336     *        <code>showProgress</code> parameter is set to <code>true</code> otherwise the first
2337     *        compatible importer will be automatically used.
2338     * @param path
2339     *        image file to load
2340     * @param series
2341     *        Series index to load (for multi series sequence), set to 0 if unsure (default).<br>
2342     *        -1 is a special value so it gives a chance to the user to select series to open from a
2343     *        series selector dialog.
2344     * @param resolution
2345     *        Wanted resolution level for the image (use 0 if unsure), useful for large image<br>
2346     *        The retrieved image resolution is equal to
2347     *        <code>image.resolution / (2^resolution)</code><br>
2348     *        So for instance level 0 is the default/full image resolution while level 1 is base
2349     *        image
2350     *        resolution / 2 and so on...
2351     * @param region
2352     *        The 2D region of the image we want to retrieve.<br>
2353     *        If set to <code>null</code> then the whole XY plane of the image is returned.
2354     * @param minZ
2355     *        the minimum Z position of the image (slice) we want retrieve (inclusive).<br>
2356     *        Set to -1 to retrieve the whole stack.
2357     * @param maxZ
2358     *        the maximum Z position of the image (slice) we want retrieve (inclusive).<br>
2359     *        Set to -1 to retrieve the whole stack.
2360     * @param minT
2361     *        the minimum T position of the image (frame) we want retrieve (inclusive).<br>
2362     *        Set to -1 to retrieve the whole timelaps.
2363     * @param maxT
2364     *        the maximum T position of the image (frame) we want retrieve (inclusive).<br>
2365     *        Set to -1 to retrieve the whole timelaps.
2366     * @param channel
2367     *        C position of the image (channel) we want retrieve (-1 means all channel).
2368     * @param separate
2369     *        Force image to be loaded in separate sequence if possible (disable stitching if any)
2370     * @param addToRecent
2371     *        If set to true the files list will be traced in recent opened sequence.
2372     * @param showProgress
2373     *        Show progression in loading process
2374     */
2375    public static void load(final SequenceFileImporter importer, final String path, final int series,
2376            final int resolution, final Rectangle region, final int minZ, final int maxZ, final int minT,
2377            final int maxT, final int channel, final boolean separate, final boolean addToRecent,
2378            final boolean showProgress)
2379    {
2380        load(importer, path, series, resolution, region, minZ, maxZ, minT, maxT, channel, false, separate, addToRecent,
2381                showProgress);
2382    }
2383
2384    /**
2385     * Load the specified image file list (try to group them as much as possible).<br>
2386     * The loading process is asynchronous.<br>
2387     * The resulting sequence is automatically displayed when the process complete.
2388     * 
2389     * @param paths
2390     *        list of file we want to group to build a to load
2391     * @param resolution
2392     *        Wanted resolution level for the image (use 0 if unsure), useful for large image<br>
2393     *        The retrieved image resolution is equal to
2394     *        <code>image.resolution / (2^resolution)</code><br>
2395     *        So for instance level 0 is the default/full image resolution while level 1 is base
2396     *        image
2397     *        resolution / 2 and so on...
2398     * @param region
2399     *        The 2D region of the image we want to retrieve.<br>
2400     *        If set to <code>null</code> then the whole XY plane of the image is returned.
2401     * @param minZ
2402     *        the minimum Z position of the image (slice) we want retrieve (inclusive).<br>
2403     *        Set to -1 to retrieve the whole stack.
2404     * @param maxZ
2405     *        the maximum Z position of the image (slice) we want retrieve (inclusive).<br>
2406     *        Set to -1 to retrieve the whole stack.
2407     * @param minT
2408     *        the minimum T position of the image (frame) we want retrieve (inclusive).<br>
2409     *        Set to -1 to retrieve the whole timelaps.
2410     * @param maxT
2411     *        the maximum T position of the image (frame) we want retrieve (inclusive).<br>
2412     *        Set to -1 to retrieve the whole timelaps.
2413     * @param channel
2414     *        C position of the image (channel) we want retrieve (-1 means all channel).
2415     * @param directory
2416     *        specify is the source is a single complete directory
2417     * @param addToRecent
2418     *        If set to true the files list will be traced in recent opened sequence.
2419     * @param showProgress
2420     *        Show progression in loading process
2421     */
2422    public static void load(final List<String> paths, final int resolution, final Rectangle region, final int minZ,
2423            final int maxZ, final int minT, final int maxT, final int channel, final boolean directory,
2424            final boolean addToRecent, final boolean showProgress)
2425    {
2426        // asynchronous call
2427        ThreadUtil.bgRun(new Runnable()
2428        {
2429            @Override
2430            public void run()
2431            {
2432                final ApplicationMenu mainMenu;
2433                final FileFrame loadingFrame;
2434
2435                if (addToRecent)
2436                    mainMenu = Icy.getMainInterface().getApplicationMenu();
2437                else
2438                    mainMenu = null;
2439                if (showProgress && !Icy.getMainInterface().isHeadLess())
2440                    loadingFrame = new FileFrame("Loading", null);
2441                else
2442                    loadingFrame = null;
2443
2444                try
2445                {
2446                    // load sequence from group with advanced options
2447                    for (SequenceFileGroup group : SequenceFileSticher.groupAllFiles(null, cleanNonImageFile(paths),
2448                            true, loadingFrame))
2449                    {
2450                        final Sequence sequence = internalLoadGroup(group, resolution, region, minZ, maxZ, minT, maxT,
2451                                channel, false, directory, mainMenu, loadingFrame);
2452
2453                        if (sequence != null)
2454                        {
2455                            // load sequence XML data
2456                            if (GeneralPreferences.getSequencePersistence())
2457                                sequence.loadXMLData();
2458                            // and display it
2459                            Icy.getMainInterface().addSequence(sequence);
2460                        }
2461                    }
2462                }
2463                catch (Throwable t)
2464                {
2465                    // just show the error
2466                    IcyExceptionHandler.showErrorMessage(t, true);
2467
2468                    if (loadingFrame != null)
2469                        new FailedAnnounceFrame((t instanceof OutOfMemoryError) ? t.getMessage()
2470                                : "Failed to open file(s), see the console output for more details.");
2471                }
2472                finally
2473                {
2474                    if (loadingFrame != null)
2475                        loadingFrame.close();
2476                }
2477            }
2478        });
2479    }
2480
2481    /**
2482     * Load the specified image file group (built using SequenceFileSticher class).<br>
2483     * The loading process is asynchronous.<br>
2484     * The resulting sequence is automatically displayed when the process complete.
2485     * 
2486     * @param group
2487     *        Sequence file group built using {@link SequenceFileSticher}.
2488     * @param resolution
2489     *        Wanted resolution level for the image (use 0 if unsure), useful for large image<br>
2490     *        The retrieved image resolution is equal to
2491     *        <code>image.resolution / (2^resolution)</code><br>
2492     *        So for instance level 0 is the default/full image resolution while level 1 is base
2493     *        image
2494     *        resolution / 2 and so on...
2495     * @param region
2496     *        The 2D region of the image we want to retrieve.<br>
2497     *        If set to <code>null</code> then the whole XY plane of the image is returned.
2498     * @param minZ
2499     *        the minimum Z position of the image (slice) we want retrieve (inclusive).<br>
2500     *        Set to -1 to retrieve the whole stack.
2501     * @param maxZ
2502     *        the maximum Z position of the image (slice) we want retrieve (inclusive).<br>
2503     *        Set to -1 to retrieve the whole stack.
2504     * @param minT
2505     *        the minimum T position of the image (frame) we want retrieve (inclusive).<br>
2506     *        Set to -1 to retrieve the whole timelaps.
2507     * @param maxT
2508     *        the maximum T position of the image (frame) we want retrieve (inclusive).<br>
2509     *        Set to -1 to retrieve the whole timelaps.
2510     * @param channel
2511     *        C position of the image (channel) we want retrieve (-1 means all channel).
2512     * @param directory
2513     *        specify is the source is a single complete directory
2514     * @param addToRecent
2515     *        If set to true the files list will be traced in recent opened sequence.
2516     * @param showProgress
2517     *        Show progression in loading process
2518     */
2519    public static void load(final SequenceFileGroup group, final int resolution, final Rectangle region, final int minZ,
2520            final int maxZ, final int minT, final int maxT, final int channel, final boolean directory,
2521            final boolean addToRecent, final boolean showProgress)
2522    {
2523        // asynchronous call
2524        ThreadUtil.bgRun(new Runnable()
2525        {
2526            @Override
2527            public void run()
2528            {
2529                final ApplicationMenu mainMenu;
2530                final FileFrame loadingFrame;
2531
2532                if (addToRecent)
2533                    mainMenu = Icy.getMainInterface().getApplicationMenu();
2534                else
2535                    mainMenu = null;
2536                if (showProgress && !Icy.getMainInterface().isHeadLess())
2537                    loadingFrame = new FileFrame("Loading", null);
2538                else
2539                    loadingFrame = null;
2540
2541                try
2542                {
2543                    // load sequence from group with advanced options
2544                    final Sequence sequence = internalLoadGroup(group, resolution, region, minZ, maxZ, minT, maxT,
2545                            channel, false, directory, mainMenu, loadingFrame);
2546
2547                    if (sequence != null)
2548                    {
2549                        // load sequence XML data
2550                        if (GeneralPreferences.getSequencePersistence())
2551                            sequence.loadXMLData();
2552                        // and display it
2553                        Icy.getMainInterface().addSequence(sequence);
2554                    }
2555                }
2556                catch (Throwable t)
2557                {
2558                    // just show the error
2559                    IcyExceptionHandler.showErrorMessage(t, true);
2560
2561                    if (loadingFrame != null)
2562                        new FailedAnnounceFrame((t instanceof OutOfMemoryError) ? t.getMessage()
2563                                : "Failed to open file(s), see the console output for more details.");
2564                }
2565                finally
2566                {
2567                    if (loadingFrame != null)
2568                        loadingFrame.close();
2569                }
2570            }
2571        });
2572    }
2573
2574    /**
2575     * Load the specified image files with the given {@link SequenceFileImporter}.<br>
2576     * The loading process is asynchronous.<br>
2577     * If <i>separate</i> is false the loader try to set image in the same sequence.<br>
2578     * If <i>separate</i> is true each image is loaded in a separate sequence.<br>
2579     * The resulting sequences are automatically displayed when the process complete.
2580     * 
2581     * @param importer
2582     *        Importer used to open and load image files.<br>
2583     *        If set to <code>null</code> the loader will search for a compatible importer and if
2584     *        several importers match the user will have to select the appropriate one from a
2585     *        selection dialog.
2586     * @param paths
2587     *        list of image file to load
2588     * @param separate
2589     *        Force image to be loaded in separate sequence
2590     * @param autoOrder
2591     *        Try to order image in sequence from their filename
2592     * @param showProgress
2593     *        Show progression in loading process
2594     */
2595    public static void load(final SequenceFileImporter importer, final List<String> paths, final boolean separate,
2596            final boolean autoOrder, final boolean showProgress)
2597    {
2598        // asynchronous call
2599        ThreadUtil.bgRun(new Runnable()
2600        {
2601            @Override
2602            public void run()
2603            {
2604                // load sequence
2605                final List<Sequence> sequences = loadSequences(importer, paths, -1, separate, autoOrder, true,
2606                        showProgress);
2607                // and display them
2608                for (Sequence seq : sequences)
2609                    Icy.getMainInterface().addSequence(seq);
2610            }
2611        });
2612    }
2613
2614    /**
2615     * Load the specified files (asynchronous process) by using automatically the appropriate
2616     * {@link FileImporter} or {@link SequenceFileImporter}. If several importers match to open the
2617     * file the user will have to select the appropriate one from a selection dialog.<br>
2618     * <br>
2619     * If the specified files are image files:<br>
2620     * When <i>separate</i> is <code>false</code> the loader try to set image in the same
2621     * sequence.<br>
2622     * When <i>separate</i> is <code>true</code> each image is loaded in a separate sequence.<br>
2623     * The resulting sequences are automatically displayed when the process complete.
2624     * 
2625     * @param paths
2626     *        list of file to load
2627     * @param forceVolatile
2628     *        If set to <code>true</code> then image data is forced to volatile (see {@link IcyBufferedImage#isVolatile()}).<br>
2629     *        Note that if you don't have enough memory to load the whole Sequence into memory then image data are always made volatile.
2630     * @param separate
2631     *        Force image to be loaded in separate sequence (image files only)
2632     * @param autoOrder
2633     *        Try to order image in sequence from their filename (image files only)
2634     * @param showProgress
2635     *        Show progression in loading process
2636     */
2637    public static void load(final List<String> paths, final boolean forceVolatile, final boolean separate,
2638            final boolean autoOrder, final boolean showProgress)
2639    {
2640        // asynchronous call
2641        ThreadUtil.bgRun(new Runnable()
2642        {
2643            @SuppressWarnings("resource")
2644            @Override
2645            public void run()
2646            {
2647                // detect if this is a complete folder load
2648                final boolean directory = (paths.size() == 1) && new File(paths.get(0)).isDirectory();
2649                // explode path list
2650                final List<String> singlePaths = cleanNonImageFile(explode(paths));
2651                final boolean adjSeparate = (singlePaths.size() <= 1) || separate;
2652                final Map<SequenceFileImporter, List<String>> sequenceFileImporters = getSequenceFileImporters(null,
2653                        singlePaths, !adjSeparate, false);
2654
2655                for (Entry<SequenceFileImporter, List<String>> entry : sequenceFileImporters.entrySet())
2656                {
2657                    final SequenceFileImporter importer = entry.getKey();
2658                    final List<String> currPaths = entry.getValue();
2659                    final boolean dir = directory && (sequenceFileImporters.size() == 1)
2660                            && (currPaths.size() == singlePaths.size());
2661
2662                    // load sequence
2663                    final List<Sequence> sequences = loadSequences(importer, currPaths, -1, forceVolatile, adjSeparate,
2664                            autoOrder, dir, true, showProgress);
2665                    // and display them
2666                    for (Sequence seq : sequences)
2667                        Icy.getMainInterface().addSequence(seq);
2668
2669                    // remove loaded files
2670                    singlePaths.removeAll(currPaths);
2671                }
2672
2673                if (singlePaths.size() > 0)
2674                {
2675                    // get the file importer now for remaining file
2676                    final Map<FileImporter, List<String>> fileImporters = getFileImporters(singlePaths, false);
2677
2678                    for (Entry<FileImporter, List<String>> entry : fileImporters.entrySet())
2679                    {
2680                        final FileImporter importer = entry.getKey();
2681                        final List<String> currPaths = entry.getValue();
2682
2683                        // load files
2684                        loadFiles(importer, paths, true, showProgress);
2685
2686                        // remove loaded files
2687                        singlePaths.removeAll(currPaths);
2688                    }
2689                }
2690
2691                // remaining files ?
2692                if (singlePaths.size() > 0)
2693                {
2694                    // get first found importer for remaining files
2695                    final Map<SequenceFileImporter, List<String>> importers = getSequenceFileImporters(singlePaths,
2696                            true);
2697
2698                    // user canceled action for these paths so we remove them
2699                    for (List<String> values : importers.values())
2700                        singlePaths.removeAll(values);
2701
2702                    if (singlePaths.size() > 0)
2703                    {
2704                        // just log in console
2705                        System.err.println("No compatible importer found for the following files:");
2706                        for (String path : singlePaths)
2707                            System.err.println(path);
2708                        System.err.println();
2709                    }
2710                }
2711            }
2712        });
2713    }
2714
2715    /**
2716     * Load the specified files (asynchronous process) by using automatically the appropriate
2717     * {@link FileImporter} or {@link SequenceFileImporter}. If several importers match to open the
2718     * file the user will have to select the appropriate one from a selection dialog.<br>
2719     * <br>
2720     * If the specified files are image files:<br>
2721     * When <i>separate</i> is <code>false</code> the loader try to set image in the same
2722     * sequence.<br>
2723     * When <i>separate</i> is <code>true</code> each image is loaded in a separate sequence.<br>
2724     * The resulting sequences are automatically displayed when the process complete.
2725     * 
2726     * @param paths
2727     *        list of file to load
2728     * @param separate
2729     *        Force image to be loaded in separate sequence (image files only)
2730     * @param autoOrder
2731     *        Try to order image in sequence from their filename (image files only)
2732     * @param showProgress
2733     *        Show progression in loading process
2734     */
2735    public static void load(final List<String> paths, final boolean separate, final boolean autoOrder,
2736            final boolean showProgress)
2737    {
2738        load(paths, false, separate, autoOrder, showProgress);
2739    }
2740
2741    /**
2742     * Load the specified file (asynchronous process) by using automatically the appropriate
2743     * {@link FileImporter} or
2744     * {@link SequenceFileImporter}. If several importers match to open the
2745     * file the user will have to select the appropriate one from a selection dialog.<br>
2746     * <br>
2747     * If the specified file is an image file, the resulting sequence is automatically displayed
2748     * when process complete.
2749     * 
2750     * @param path
2751     *        file to load
2752     * @param showProgress
2753     *        Show progression of loading process.
2754     */
2755    public static void load(String path, boolean showProgress)
2756    {
2757        load(CollectionUtil.createArrayList(path), false, false, showProgress);
2758    }
2759
2760    /**
2761     * @deprecated Use {@link #loadSequences(List, int, boolean, boolean, boolean, boolean)}
2762     *             instead.
2763     */
2764    @Deprecated
2765    static Sequence[] loadSequences(SequenceFileImporter importer, File[] files, int series, boolean separate,
2766            boolean autoOrder, boolean directory, boolean addToRecent, boolean showProgress)
2767    {
2768        final List<String> paths = cleanNonImageFile(CollectionUtil.asList(FileUtil.toPaths(files)));
2769        final List<Sequence> result = loadSequences(importer, paths, series, false, separate, autoOrder, directory,
2770                addToRecent, showProgress);
2771        return result.toArray(new Sequence[result.size()]);
2772    }
2773
2774    /**
2775     * Loads the specified image file and return it as a Sequence, it can return <code>null</code>
2776     * if an error occured.<br>
2777     * As this method can take sometime, you should not call it from the EDT.
2778     * 
2779     * @param importer
2780     *        Importer used to load the image file.<br>
2781     *        If set to <code>null</code> the loader will search for a compatible importer and if
2782     *        several importers
2783     *        match the user will have to select the appropriate one from a selection dialog if
2784     *        <code>showProgress</code> parameter is set to <code>true</code> otherwise the first
2785     *        compatible importer will be automatically used.
2786     * @param path
2787     *        image file to load
2788     * @param series
2789     *        Series index to load (for multi series sequence), set to 0 if unsure (default).<br>
2790     *        -1 is a special value so it gives a chance to the user to select series to open from a
2791     *        series selector dialog.
2792     * @param resolution
2793     *        Wanted resolution level for the image (use 0 if unsure), useful for large image<br>
2794     *        The retrieved image resolution is equal to <code>image.resolution / (2^resolution)</code><br>
2795     *        So for instance level 0 is the default/full image resolution while level 1 is base image resolution / 2
2796     *        and so on...
2797     * @param region
2798     *        The 2D region of the image we want to retrieve (in full image resolution).<br>
2799     *        If set to <code>null</code> then the whole XY plane of the image is returned.
2800     * @param minZ
2801     *        the minimum Z position of the image (slice) we want retrieve (inclusive).<br>
2802     *        Set to -1 to retrieve the whole stack.
2803     * @param maxZ
2804     *        the maximum Z position of the image (slice) we want retrieve (inclusive).<br>
2805     *        Set to -1 to retrieve the whole stack.
2806     * @param minT
2807     *        the minimum T position of the image (frame) we want retrieve (inclusive).<br>
2808     *        Set to -1 to retrieve the whole timelaps.
2809     * @param maxT
2810     *        the maximum T position of the image (frame) we want retrieve (inclusive).<br>
2811     *        Set to -1 to retrieve the whole timelaps.
2812     * @param channel
2813     *        C position of the image (channel) we want retrieve (-1 means all channel).
2814     * @param forceVolatile
2815     *        If set to <code>true</code> then image data is forced to volatile (see {@link IcyBufferedImage#isVolatile()}).<br>
2816     *        Note that if you don't have enough memory to load the whole Sequence into memory then image data are always made volatile.
2817     * @param addToRecent
2818     *        If set to true the files list will be traced in recent opened sequence.
2819     * @param showProgress
2820     *        Show progression in loading process
2821     */
2822    public static Sequence loadSequence(SequenceFileImporter importer, String path, int series, int resolution,
2823            Rectangle region, int minZ, int maxZ, int minT, int maxT, int channel, boolean forceVolatile,
2824            boolean addToRecent, boolean showProgress)
2825    {
2826        final ApplicationMenu mainMenu;
2827        final FileFrame loadingFrame;
2828        final Sequence result;
2829
2830        if (addToRecent)
2831            mainMenu = Icy.getMainInterface().getApplicationMenu();
2832        else
2833            mainMenu = null;
2834        if (showProgress && !Icy.getMainInterface().isHeadLess())
2835            loadingFrame = new FileFrame("Loading", path);
2836        else
2837            loadingFrame = null;
2838
2839        // importer is not specified ? --> get a compatible one.
2840        final SequenceFileImporter imp = (importer == null) ? getSequenceFileImporter(path, !showProgress) : importer;
2841
2842        try
2843        {
2844            // open image
2845            imp.open(path, 0);
2846
2847            // get metadata
2848            final OMEXMLMetadata meta = imp.getOMEXMLMetaData();
2849            // clean the metadata
2850            MetaDataUtil.clean(meta);
2851
2852            // series selection
2853            int selectedSerie;
2854
2855            // give the opportunity to select the series (single one) to open ?
2856            if (series == -1)
2857            {
2858                try
2859                {
2860                    // series selection (create a new importer instance as selectSerie(..) does async processes)
2861                    selectedSerie = selectSerie(cloneSequenceFileImporter(imp), path, meta, 0);
2862                }
2863                catch (Throwable t)
2864                {
2865                    IcyExceptionHandler.showErrorMessage(t, true, true);
2866                    System.err.print("Opening first series by default...");
2867                    selectedSerie = 0;
2868                }
2869
2870                // user cancelled action in the series selection ? null = cancel
2871                if (selectedSerie == -1)
2872                    return null;
2873            }
2874            else
2875                selectedSerie = series;
2876
2877            // load the image
2878            result = internalLoadSingle(imp, meta, selectedSerie, resolution, region, minZ, maxZ, minT, maxT, channel,
2879                    forceVolatile, loadingFrame);
2880
2881            // Don't close importer on success ! we want to keep it inside the sequence.
2882            // We will close it when finalizing the sequence...
2883            // imp.close();
2884
2885            // add as separate item to recent file list
2886            if (mainMenu != null)
2887                mainMenu.addRecentLoadedFile(new File(FileUtil.getGenericPath(path)));
2888
2889            // TODO: restore colormap --> try to recover colormap
2890
2891            // load sequence XML data
2892            if (GeneralPreferences.getSequencePersistence())
2893                result.loadXMLData();
2894        }
2895        catch (Throwable t)
2896        {
2897            try
2898            {
2899                // close importer when error happen (not stored in Sequence so we need to close it manually)
2900                imp.close();
2901            }
2902            catch (Exception e)
2903            {
2904                // ignore
2905            }
2906
2907            // just show the error
2908            IcyExceptionHandler.showErrorMessage(t, true);
2909
2910            if (loadingFrame != null)
2911            {
2912                new FailedAnnounceFrame((t instanceof OutOfMemoryError) ? t.getMessage()
2913                        : "Failed to open file(s), see the console output for more details.");
2914            }
2915
2916            return null;
2917        }
2918        finally
2919        {
2920            if (loadingFrame != null)
2921                loadingFrame.close();
2922        }
2923
2924        return result;
2925    }
2926
2927    /**
2928     * Loads the specified image file and return it as a Sequence, it can return <code>null</code>
2929     * if an error occured.<br>
2930     * As this method can take sometime, you should not call it from the EDT.
2931     * 
2932     * @param importer
2933     *        Importer used to load the image file.<br>
2934     *        If set to <code>null</code> the loader will search for a compatible importer and if
2935     *        several importers
2936     *        match the user will have to select the appropriate one from a selection dialog if
2937     *        <code>showProgress</code> parameter is set to <code>true</code> otherwise the first
2938     *        compatible importer will be automatically used.
2939     * @param path
2940     *        image file to load
2941     * @param series
2942     *        Series index to load (for multi series sequence), set to 0 if unsure (default).<br>
2943     *        -1 is a special value so it gives a chance to the user to select series to open from a
2944     *        series selector dialog.
2945     * @param resolution
2946     *        Wanted resolution level for the image (use 0 if unsure), useful for large image<br>
2947     *        The retrieved image resolution is equal to <code>image.resolution / (2^resolution)</code><br>
2948     *        So for instance level 0 is the default/full image resolution while level 1 is base image resolution / 2
2949     *        and so on...
2950     * @param region
2951     *        The 2D region of the image we want to retrieve (in full image resolution).<br>
2952     *        If set to <code>null</code> then the whole XY plane of the image is returned.
2953     * @param minZ
2954     *        the minimum Z position of the image (slice) we want retrieve (inclusive).<br>
2955     *        Set to -1 to retrieve the whole stack.
2956     * @param maxZ
2957     *        the maximum Z position of the image (slice) we want retrieve (inclusive).<br>
2958     *        Set to -1 to retrieve the whole stack.
2959     * @param minT
2960     *        the minimum T position of the image (frame) we want retrieve (inclusive).<br>
2961     *        Set to -1 to retrieve the whole timelaps.
2962     * @param maxT
2963     *        the maximum T position of the image (frame) we want retrieve (inclusive).<br>
2964     *        Set to -1 to retrieve the whole timelaps.
2965     * @param channel
2966     *        C position of the image (channel) we want retrieve (-1 means all channel).
2967     * @param addToRecent
2968     *        If set to true the files list will be traced in recent opened sequence.
2969     * @param showProgress
2970     *        Show progression in loading process
2971     */
2972    public static Sequence loadSequence(SequenceFileImporter importer, String path, int series, int resolution,
2973            Rectangle region, int minZ, int maxZ, int minT, int maxT, int channel, boolean addToRecent,
2974            boolean showProgress)
2975    {
2976        return loadSequence(importer, path, series, resolution, region, minZ, maxZ, minT, maxT, channel, false,
2977                addToRecent, showProgress);
2978    }
2979
2980    /**
2981     * Load a sequence from the specified list of file and returns it.<br>
2982     * The function try to guess image ordering from file name and metadata.<br>
2983     * 
2984     * @param importer
2985     *        Importer used to load the image files.<br>
2986     *        If set to <code>null</code> the loader will take the first compatible importer found.
2987     * @param paths
2988     *        list of file where to load image from
2989     * @param resolution
2990     *        Wanted resolution level for the image (use 0 if unsure), useful for large image<br>
2991     *        The retrieved image resolution is equal to
2992     *        <code>image.resolution / (2^resolution)</code><br>
2993     *        So for instance level 0 is the default/full image resolution while level 1 is base
2994     *        image
2995     *        resolution / 2 and so on...
2996     * @param region
2997     *        The 2D region of the image we want to retrieve.<br>
2998     *        If set to <code>null</code> then the whole XY plane of the image is returned.
2999     * @param minZ
3000     *        the minimum Z position of the image (slice) we want retrieve (inclusive).<br>
3001     *        Set to -1 to retrieve the whole stack.
3002     * @param maxZ
3003     *        the maximum Z position of the image (slice) we want retrieve (inclusive).<br>
3004     *        Set to -1 to retrieve the whole stack.
3005     * @param minT
3006     *        the minimum T position of the image (frame) we want retrieve (inclusive).<br>
3007     *        Set to -1 to retrieve the whole timelaps.
3008     * @param maxT
3009     *        the maximum T position of the image (frame) we want retrieve (inclusive).<br>
3010     *        Set to -1 to retrieve the whole timelaps.
3011     * @param channel
3012     *        C position of the image (channel) we want retrieve (-1 means all channel).
3013     * @param forceVolatile
3014     *        If set to <code>true</code> then image data is forced to volatile (see {@link IcyBufferedImage#isVolatile()}).<br>
3015     *        Note that if you don't have enough memory to load the whole Sequence into memory then image data are always made volatile.
3016     * @param directory
3017     *        specify is the source is a single complete directory
3018     * @param addToRecent
3019     *        If set to true the files list will be traced in recent opened sequence.
3020     * @param showProgress
3021     *        Show progression in loading process
3022     * @see #getSequenceFileImporter(String, boolean)
3023     */
3024    public static Sequence loadSequence(SequenceFileImporter importer, List<String> paths, int resolution,
3025            Rectangle region, int minZ, int maxZ, int minT, int maxT, int channel, boolean forceVolatile,
3026            boolean directory, boolean addToRecent, boolean showProgress)
3027    {
3028        final ApplicationMenu mainMenu;
3029        final FileFrame loadingFrame;
3030        Sequence result = null;
3031
3032        if (addToRecent)
3033            mainMenu = Icy.getMainInterface().getApplicationMenu();
3034        else
3035            mainMenu = null;
3036        if (showProgress && !Icy.getMainInterface().isHeadLess())
3037            loadingFrame = new FileFrame("Loading", null);
3038        else
3039            loadingFrame = null;
3040
3041        try
3042        {
3043            // we want only one group
3044            final SequenceFileGroup group = SequenceFileSticher.groupFiles(importer, cleanNonImageFile(paths), true,
3045                    loadingFrame);
3046            // do group loading
3047            result = internalLoadGroup(group, resolution, region, minZ, maxZ, minT, maxT, channel, forceVolatile,
3048                    directory, mainMenu, loadingFrame);
3049            // load sequence XML data
3050            if (GeneralPreferences.getSequencePersistence())
3051                result.loadXMLData();
3052        }
3053        catch (Throwable t)
3054        {
3055            // just show the error
3056            IcyExceptionHandler.showErrorMessage(t, true);
3057
3058            if (loadingFrame != null)
3059                new FailedAnnounceFrame((t instanceof OutOfMemoryError) ? t.getMessage()
3060                        : "Failed to open file(s), see the console output for more details.");
3061        }
3062        finally
3063        {
3064            if (loadingFrame != null)
3065                loadingFrame.close();
3066        }
3067
3068        return result;
3069    }
3070
3071    /**
3072     * Load a sequence from the specified list of file and returns it.<br>
3073     * The function try to guess image ordering from file name and metadata.<br>
3074     * 
3075     * @param paths
3076     *        list of file where to load image from
3077     * @param resolution
3078     *        Wanted resolution level for the image (use 0 if unsure), useful for large image<br>
3079     *        The retrieved image resolution is equal to
3080     *        <code>image.resolution / (2^resolution)</code><br>
3081     *        So for instance level 0 is the default/full image resolution while level 1 is base
3082     *        image
3083     *        resolution / 2 and so on...
3084     * @param region
3085     *        The 2D region of the image we want to retrieve.<br>
3086     *        If set to <code>null</code> then the whole XY plane of the image is returned.
3087     * @param minZ
3088     *        the minimum Z position of the image (slice) we want retrieve (inclusive).<br>
3089     *        Set to -1 to retrieve the whole stack.
3090     * @param maxZ
3091     *        the maximum Z position of the image (slice) we want retrieve (inclusive).<br>
3092     *        Set to -1 to retrieve the whole stack.
3093     * @param minT
3094     *        the minimum T position of the image (frame) we want retrieve (inclusive).<br>
3095     *        Set to -1 to retrieve the whole timelaps.
3096     * @param maxT
3097     *        the maximum T position of the image (frame) we want retrieve (inclusive).<br>
3098     *        Set to -1 to retrieve the whole timelaps.
3099     * @param channel
3100     *        C position of the image (channel) we want retrieve (-1 means all channel).
3101     * @param forceVolatile
3102     *        If set to <code>true</code> then image data is forced to volatile (see {@link IcyBufferedImage#isVolatile()}).<br>
3103     *        Note that if you don't have enough memory to load the whole Sequence into memory then image data are always made volatile.
3104     * @param directory
3105     *        specify is the source is a single complete directory
3106     * @param addToRecent
3107     *        If set to true the files list will be traced in recent opened sequence.
3108     * @param showProgress
3109     *        Show progression in loading process
3110     * @see #getSequenceFileImporter(String, boolean)
3111     */
3112    public static Sequence loadSequence(List<String> paths, int resolution, Rectangle region, int minZ, int maxZ,
3113            int minT, int maxT, int channel, boolean forceVolatile, boolean directory, boolean addToRecent,
3114            boolean showProgress)
3115    {
3116        return loadSequence(null, paths, resolution, region, minZ, maxZ, minT, maxT, channel, forceVolatile, directory,
3117                addToRecent, showProgress);
3118    }
3119
3120    /**
3121     * Load a sequence from the specified list of file and returns it.<br>
3122     * The function try to guess image ordering from file name and metadata.<br>
3123     * 
3124     * @param paths
3125     *        list of file where to load image from
3126     * @param resolution
3127     *        Wanted resolution level for the image (use 0 if unsure), useful for large image<br>
3128     *        The retrieved image resolution is equal to
3129     *        <code>image.resolution / (2^resolution)</code><br>
3130     *        So for instance level 0 is the default/full image resolution while level 1 is base
3131     *        image
3132     *        resolution / 2 and so on...
3133     * @param region
3134     *        The 2D region of the image we want to retrieve.<br>
3135     *        If set to <code>null</code> then the whole XY plane of the image is returned.
3136     * @param minZ
3137     *        the minimum Z position of the image (slice) we want retrieve (inclusive).<br>
3138     *        Set to -1 to retrieve the whole stack.
3139     * @param maxZ
3140     *        the maximum Z position of the image (slice) we want retrieve (inclusive).<br>
3141     *        Set to -1 to retrieve the whole stack.
3142     * @param minT
3143     *        the minimum T position of the image (frame) we want retrieve (inclusive).<br>
3144     *        Set to -1 to retrieve the whole timelaps.
3145     * @param maxT
3146     *        the maximum T position of the image (frame) we want retrieve (inclusive).<br>
3147     *        Set to -1 to retrieve the whole timelaps.
3148     * @param channel
3149     *        C position of the image (channel) we want retrieve (-1 means all channel).
3150     * @param directory
3151     *        specify is the source is a single complete directory
3152     * @param addToRecent
3153     *        If set to true the files list will be traced in recent opened sequence.
3154     * @param showProgress
3155     *        Show progression in loading process
3156     * @see #getSequenceFileImporter(String, boolean)
3157     */
3158    public static Sequence loadSequence(List<String> paths, int resolution, Rectangle region, int minZ, int maxZ,
3159            int minT, int maxT, int channel, boolean directory, boolean addToRecent, boolean showProgress)
3160    {
3161        return loadSequence(null, paths, resolution, region, minZ, maxZ, minT, maxT, channel, false, directory,
3162                addToRecent, showProgress);
3163    }
3164
3165    /**
3166     * Loads the specified image files and return them as list of sequence.<br>
3167     * If 'separate' is false the loader try to set images in the same sequence.<br>
3168     * If separate is true each image is loaded in a separate sequence.<br>
3169     * As this method can take sometime, you should not call it from the EDT.<br>
3170     * 
3171     * @param importer
3172     *        Importer used to open and load images (cannot be <code>null</code> here) except when separate is false
3173     * @param paths
3174     *        list of image file to load
3175     * @param series
3176     *        Series index to load (for multi series sequence), set to 0 if unsure (default).<br>
3177     *        -1 is a special value so it gives a chance to the user to select series to open from a
3178     *        series selector dialog.
3179     * @param forceVolatile
3180     *        If set to <code>true</code> then image data is forced to volatile (see {@link IcyBufferedImage#isVolatile()}).<br>
3181     *        Note that if you don't have enough memory to load the whole Sequence into memory then image data are always made volatile.
3182     * @param separate
3183     *        Force image to be loaded in separate sequence (also disable stitching if possible)
3184     * @param autoOrder
3185     *        If set to true then images are automatically orderer from their filename.
3186     * @param directory
3187     *        Specify is the source is a single complete directory
3188     * @param addToRecent
3189     *        If set to true the files list will be traced in recent opened sequence.
3190     * @param showProgress
3191     *        Show progression in loading process
3192     */
3193    static List<Sequence> loadSequences(SequenceFileImporter importer, List<String> paths, int series,
3194            boolean forceVolatile, boolean separate, boolean autoOrder, boolean directory, boolean addToRecent,
3195            boolean showProgress)
3196    {
3197        final List<Sequence> result = new ArrayList<Sequence>();
3198
3199        // nothing to load
3200        if (paths.size() <= 0)
3201            return result;
3202
3203        final ApplicationMenu mainMenu;
3204        final FileFrame loadingFrame;
3205
3206        if (addToRecent)
3207            mainMenu = Icy.getMainInterface().getApplicationMenu();
3208        else
3209            mainMenu = null;
3210        if (showProgress && !Icy.getMainInterface().isHeadLess())
3211            loadingFrame = new FileFrame("Loading", null);
3212        else
3213            loadingFrame = null;
3214
3215        try
3216        {
3217            final List<String> remainingFiles = new ArrayList<String>(paths);
3218
3219            // load each file in a separate sequence
3220            if (separate || (paths.size() <= 1))
3221            {
3222                if (loadingFrame != null)
3223                {
3224                    // each file can contains several image so we use 100 "inter-step"
3225                    loadingFrame.setLength(paths.size() * 100d);
3226                    loadingFrame.setPosition(0d);
3227                }
3228
3229                // force un-grouping when 'separate' is true
3230                if (separate && (importer instanceof LociImporterPlugin))
3231                    ((LociImporterPlugin) importer).setGroupFiles(false);
3232
3233                // load each file in a separate sequence
3234                for (String path : paths)
3235                {
3236                    // load the file
3237                    final List<Sequence> sequences = internalLoadSingle(importer, path, series, forceVolatile,
3238                            !separate, loadingFrame);
3239
3240                    // special case where loading was interrupted
3241                    if (sequences == null)
3242                    {
3243                        // no error
3244                        remainingFiles.clear();
3245                        break;
3246                    }
3247
3248                    if (sequences.size() > 0)
3249                    {
3250                        // add sequences to result
3251                        result.addAll(sequences);
3252                        // remove path from remaining
3253                        remainingFiles.remove(path);
3254                        // add as separate item to recent file list
3255                        if (mainMenu != null)
3256                            mainMenu.addRecentLoadedFile(new File(FileUtil.getGenericPath(path)));
3257                    }
3258
3259                    // interrupt loading
3260                    if ((loadingFrame != null) && loadingFrame.isCancelRequested())
3261                    {
3262                        // no error
3263                        remainingFiles.clear();
3264                        break;
3265                    }
3266                }
3267            }
3268            // grouped loading
3269            else
3270            {
3271                // get file group
3272                final Collection<SequenceFileGroup> groups = SequenceFileSticher.groupAllFiles(importer, paths,
3273                        autoOrder, loadingFrame);
3274
3275                for (SequenceFileGroup group : groups)
3276                {
3277                    final Sequence sequence = internalLoadGroup(group, forceVolatile, directory, mainMenu,
3278                            loadingFrame);
3279
3280                    // loading interrupted ?
3281                    if ((loadingFrame != null) && loadingFrame.isCancelRequested())
3282                    {
3283                        // no error
3284                        remainingFiles.clear();
3285                        break;
3286                    }
3287
3288                    // remove all paths from the group in remaining list
3289                    for (SequencePosition pos : group.positions)
3290                        remainingFiles.remove(pos.getPath());
3291
3292                    // add to result
3293                    result.add(sequence);
3294                }
3295
3296                // if (loadingFrame != null)
3297                // {
3298                // loadingFrame.setAction("Loading");
3299                // // each file can contains several image so we use 100 "inter step"
3300                // loadingFrame.setLength(filePositions.size() * 100d);
3301                // loadingFrame.setPosition(0d);
3302                // }
3303                //
3304                // for (FilePosition filePos : filePositions)
3305                // {
3306                // final String path = filePos.path;
3307                // // load the file
3308                // final List<Sequence> sequences = internalLoadSingle(importer, path, series, loadingFrame);
3309                //
3310                // // special case where loading was interrupted
3311                // if (sequences == null)
3312                // {
3313                // // no error
3314                // remainingFiles.clear();
3315                // break;
3316                // }
3317                //
3318                // final int s = filePos.getS();
3319                // final int z = filePos.getZ();
3320                // final int t = filePos.getT();
3321                // final int c = filePos.getC();
3322                // boolean concat;
3323                //
3324                // // special case of single result --> try to concatenate to last sequence
3325                // if ((sequences.size() == 1) && !map.isEmpty())
3326                // {
3327                // final Sequence seq = sequences.get(0);
3328                // final int sizeZ = seq.getSizeZ();
3329                // final int sizeT = seq.getSizeT();
3330                // final int sizeC = seq.getSizeC();
3331                //
3332                // concat = true;
3333                // // concatenation restriction
3334                // if (lastS != s)
3335                // concat = false;
3336                // if ((sizeZ > 1) && (z > 0))
3337                // concat = false;
3338                // if ((sizeT > 1) && (t > 0))
3339                // concat = false;
3340                // if ((sizeC > 1) && (c > 0))
3341                // concat = false;
3342                //
3343                // if (concat)
3344                // {
3345                // // find last sequence for this channel
3346                // final Sequence lastSequence = map.get(Integer.valueOf(c));
3347                //
3348                // // determine if concatenation is possible
3349                // if ((lastSequence != null) && !lastSequence.isCompatible(seq.getFirstImage()))
3350                // concat = false;
3351                // }
3352                //
3353                // // update series index
3354                // lastS = s;
3355                // }
3356                // else
3357                // concat = false;
3358                //
3359                // // sequence correctly loaded ?
3360                // if (sequences.size() > 0)
3361                // {
3362                // if (concat)
3363                // {
3364                // final Sequence seq = sequences.get(0);
3365                // // find last sequence for this channel
3366                // Sequence lastSequence = map.get(Integer.valueOf(c));
3367                //
3368                // // concatenate
3369                // lastSequence = concatenateSequence(lastSequence, seq, t > 0, z > 0);
3370                // // store the merged sequence for this channel
3371                // map.put(Integer.valueOf(c), lastSequence);
3372                // }
3373                // else
3374                // {
3375                // // concatenate sequences in map and add it to result list
3376                // addSequences(result, map);
3377                // // if on first channel then put the last sequence result in the map
3378                // if (c == 0)
3379                // map.put(Integer.valueOf(0), sequences.remove(sequences.size() - 1));
3380                // // and add the rest to the list
3381                // if (sequences.size() > 0)
3382                // result.addAll(sequences);
3383                // }
3384                //
3385                // // remove path from remaining
3386                // remainingFiles.remove(path);
3387                // }
3388                //
3389                // // interrupt loading
3390                // if ((loadingFrame != null) && loadingFrame.isCancelRequested())
3391                // {
3392                // // no error
3393                // remainingFiles.clear();
3394                // break;
3395                // }
3396                // }
3397                //
3398                // // concatenate last sequences in map and add it to result list
3399                // addSequences(result, map);
3400                //
3401                // // add as one item to recent file list
3402                // if (mainMenu != null)
3403                // {
3404                // // set only the directory entry
3405                // if (directory)
3406                // mainMenu.addRecentFile(FileUtil.getDirectory(paths.get(0), false));
3407                // else
3408                // mainMenu.addRecentFile(paths);
3409                // }
3410            }
3411
3412            // TODO: restore colormap --> try to recover colormap
3413
3414            if (remainingFiles.size() > 0)
3415            {
3416                System.err.println("Cannot open the following file(s) (format not supported):");
3417                for (String path : remainingFiles)
3418                    System.err.println(path);
3419
3420                if (loadingFrame != null)
3421                {
3422                    new FailedAnnounceFrame(
3423                            "Some file(s) could not be opened (format not supported). See the console output for more details.");
3424                }
3425            }
3426
3427            // load sequence XML data
3428            if (GeneralPreferences.getSequencePersistence())
3429            {
3430                for (Sequence seq : result)
3431                    seq.loadXMLData();
3432            }
3433        }
3434        catch (Throwable t)
3435        {
3436            // just show the error
3437            IcyExceptionHandler.showErrorMessage(t, true);
3438
3439            if (loadingFrame != null)
3440                new FailedAnnounceFrame((t instanceof OutOfMemoryError) ? t.getMessage()
3441                        : "Failed to open file(s), see the console output for more details.");
3442        }
3443        finally
3444        {
3445            if (loadingFrame != null)
3446                loadingFrame.close();
3447        }
3448
3449        return result;
3450    }
3451
3452    /**
3453     * Concatenate the <i>src</i> sequence to the <i>dest</i> one.
3454     */
3455    static Sequence concatenateSequence(Sequence dest, Sequence src, boolean onT, boolean onZ)
3456    {
3457        if (dest == null)
3458            return src;
3459        if (src == null)
3460            return dest;
3461
3462        final int dst;
3463        final int dsz;
3464        final int sst = src.getSizeT();
3465        final int ssz = src.getSizeZ();
3466
3467        if (onT)
3468        {
3469            if (onZ)
3470            {
3471                dst = dest.getSizeT() - 1;
3472                dsz = dest.getSizeZ(dst);
3473            }
3474            else
3475            {
3476                dst = dest.getSizeT();
3477                dsz = 0;
3478            }
3479        }
3480        else
3481        {
3482            dst = 0;
3483            dsz = onZ ? dest.getSizeZ() : 0;
3484        }
3485
3486        // put 'dest' in update state to avoid useless recalculations
3487        if (!dest.isUpdating())
3488            dest.beginUpdate();
3489
3490        for (int t = 0; t < sst; t++)
3491            for (int z = 0; z < ssz; z++)
3492                dest.setImage(t + dst, z + dsz, src.getImage(t, z));
3493
3494        return dest;
3495    }
3496
3497    static void addSequences(List<Sequence> result, TreeMap<Integer, Sequence> map)
3498    {
3499        if (!map.isEmpty())
3500        {
3501            // get all sequence from the map orderer by channel
3502            final Collection<Sequence> sequencesC = map.values();
3503
3504            // remove update state
3505            for (Sequence seq : sequencesC)
3506                if (seq.isUpdating())
3507                    seq.endUpdate();
3508
3509            final Sequence sequences[] = sequencesC.toArray(new Sequence[sequencesC.size()]);
3510
3511            // several sequences ?
3512            if (sequences.length > 1)
3513            {
3514                // concatenate sequences on C dimension
3515                final Sequence merged = SequenceUtil.concatC(sequences);
3516                // better to keep name from first image
3517                merged.setName(sequences[0].getName());
3518                // then add the result to the list
3519                result.add(merged);
3520            }
3521            else
3522                result.add(sequences[0]);
3523
3524            // better to not merge the C channel after all
3525            // result.addAll(sequencesC);
3526
3527            // clear the map
3528            map.clear();
3529        }
3530    }
3531
3532    /**
3533     * <b>Internal use only !</b><br>
3534     * Load image(s) from the given importer and parameters and return the result as a Sequence.<br>
3535     * If <i>loadingFrame</i> is not <code>null</code> then it has 100 steps allocated to the
3536     * loading of current path.
3537     * 
3538     * @param importer
3539     *        Opened importer used to load the image file (cannot be <code>null</code> here)
3540     * @param metadata
3541     *        Metadata of the image
3542     * @param series
3543     *        Series index to load (for multi series sequence), set to 0 if unsure (default).
3544     * @param resolution
3545     *        Wanted resolution level for the image (use 0 if unsure), useful for large image<br>
3546     *        The retrieved image resolution is equal to <code>image.resolution / (2^resolution)</code><br>
3547     *        So for instance level 0 is the default/full image resolution while level 1 is base image resolution / 2
3548     *        and so on...
3549     * @param region
3550     *        The 2D region of the image we want to retrieve (in full image resolution).<br>
3551     *        If set to <code>null</code> then the whole XY plane of the image is returned.
3552     * @param minZ
3553     *        the minimum Z position of the image (slice) we want retrieve (inclusive).<br>
3554     *        Set to -1 to retrieve the whole stack.
3555     * @param maxZ
3556     *        the maximum Z position of the image (slice) we want retrieve (inclusive).<br>
3557     *        Set to -1 to retrieve the whole stack.
3558     * @param minT
3559     *        the minimum T position of the image (frame) we want retrieve (inclusive).<br>
3560     *        Set to -1 to retrieve the whole timelaps.
3561     * @param maxT
3562     *        the maximum T position of the image (frame) we want retrieve (inclusive).<br>
3563     *        Set to -1 to retrieve the whole timelaps.
3564     * @param channel
3565     *        C position of the image (channel) we want retrieve (-1 means all channel).
3566     * @param forceVolatile
3567     *        If set to <code>true</code> then image data is forced to volatile (see {@link IcyBufferedImage#isVolatile()}).<br>
3568     *        Note that if you don't have enough memory to load the whole Sequence into memory then image data are always made volatile.
3569     * @param loadingFrame
3570     *        the loading frame used to display progress of the operation (can be null).<br>
3571     *        Caller should allocate 100 positions for the internal single load process.
3572     * @return the Sequence object or <code>null</code>
3573     */
3574    public static Sequence internalLoadSingle(SequenceIdImporter importer, OMEXMLMetadata metadata, int series,
3575            int resolution, Rectangle region, int minZ, int maxZ, int minT, int maxT, int channel,
3576            boolean forceVolatile, FileFrame loadingFrame)
3577            throws IOException, UnsupportedFormatException, OutOfMemoryError
3578    {
3579        final int imgSizeX = MetaDataUtil.getSizeX(metadata, series);
3580        final int imgSizeY = MetaDataUtil.getSizeY(metadata, series);
3581
3582        final Rectangle adjRegion;
3583
3584        if (region != null)
3585            adjRegion = new Rectangle(0, 0, imgSizeX, imgSizeY).intersection(region);
3586        else
3587            adjRegion = null;
3588
3589        final int sizeX = (adjRegion == null) ? imgSizeX : adjRegion.width;
3590        final int sizeY = (adjRegion == null) ? imgSizeY : adjRegion.height;
3591        final int sizeZ = MetaDataUtil.getSizeZ(metadata, series);
3592        final int sizeT = MetaDataUtil.getSizeT(metadata, series);
3593        final int sizeC = MetaDataUtil.getSizeC(metadata, series);
3594        final DataType dataType = MetaDataUtil.getDataType(metadata, series);
3595
3596        final int adjMinZ, adjMaxZ;
3597        final int adjMinT, adjMaxT;
3598
3599        if (minZ < 0)
3600            adjMinZ = 0;
3601        else
3602            adjMinZ = Math.min(minZ, sizeZ);
3603        if (maxZ < 0)
3604            adjMaxZ = sizeZ - 1;
3605        else
3606            adjMaxZ = Math.min(maxZ, sizeZ - 1);
3607        if (minT < 0)
3608            adjMinT = 0;
3609        else
3610            adjMinT = Math.min(minT, sizeT);
3611        if (maxT < 0)
3612            adjMaxT = sizeT - 1;
3613        else
3614            adjMaxT = Math.min(maxT, sizeT - 1);
3615
3616        // we want volatile image
3617        boolean volatileImage = forceVolatile || GeneralPreferences.getVirtualMode();
3618
3619        try
3620        {
3621            // volatile image ? --> we just need to check the plane size
3622            if (volatileImage)
3623                checkOpeningPlane(resolution, sizeX, sizeY,
3624                        " Try to open a sub resolution or sub part of the image only.");
3625            else
3626                // check that can open the image
3627                checkOpening(resolution, sizeX, sizeY, (channel == -1) ? sizeC : 1, (adjMaxZ - adjMinZ) + 1,
3628                        (adjMaxT - adjMinT) + 1, dataType,
3629                        " Try to open a sub resolution or sub part of the image only.");
3630        }
3631        catch (OutOfMemoryError e)
3632        {
3633            // force volatile image if we don't have enough memory to open the image
3634            volatileImage = true;
3635        }
3636
3637        // create result sequence with desired series metadata
3638        final Sequence result = new Sequence(OMEUtil.createOMEXMLMetadata(metadata, series));
3639
3640        // setup sequence properties and metadata from the opening setting
3641        setupSequence(result, importer, MetaDataUtil.getNumSeries(metadata) > 1, series, adjRegion, resolution, sizeZ,
3642                sizeT, sizeC, adjMinZ, adjMaxZ, adjMinT, adjMaxT, channel);
3643
3644        // number of image to process
3645        final int numImage = ((adjMaxZ - adjMinZ) + 1) * ((adjMaxT - adjMinT) + 1);
3646
3647        if (numImage > 0)
3648        {
3649            // set local length for loader frame
3650            final double progressStep = 100d / numImage;
3651            double progress = 0d;
3652            boolean first = true;
3653            final int resDivisor = Math.max(1, (int) Math.pow(2, resolution));
3654            final int adjSizeX = sizeX / resDivisor;
3655            final int adjSizeY = sizeY / resDivisor;
3656
3657            if (loadingFrame != null)
3658                progress = loadingFrame.getPosition();
3659
3660            result.beginUpdate();
3661            try
3662            {
3663                for (int t = adjMinT; t <= adjMaxT; t++)
3664                {
3665                    for (int z = adjMinZ; z <= adjMaxZ; z++)
3666                    {
3667                        if (loadingFrame != null)
3668                        {
3669                            // cancel requested ? --> stop loading here...
3670                            if (loadingFrame.isCancelRequested())
3671                                return result;
3672
3673                            // special group importer ? --> use internal file path
3674                            if (importer instanceof SequenceFileGroupImporter)
3675                                loadingFrame.setFilename(((SequenceFileGroupImporter) importer).getPath(z, t,
3676                                        (channel != -1) ? channel : 0));
3677                        }
3678
3679                        final IcyBufferedImage image;
3680
3681                        // need to load one image at least to get the colormap information (stored in image colormodel)
3682                        if (first)
3683                        {
3684                            // load image now
3685                            if (channel == -1)
3686                                image = importer.getImage(series, resolution, adjRegion, z, t);
3687                            else
3688                                image = importer.getImage(series, resolution, adjRegion, z, t, channel);
3689
3690                            // don't forget to set volatile state
3691                            image.setVolatile(volatileImage);
3692                            // first image has been loaded now
3693                            first = false;
3694                        }
3695                        else
3696                            // use empty image for now (lazy loading)
3697                            image = new IcyBufferedImage(adjSizeX, adjSizeY, sizeC, dataType, volatileImage);
3698
3699                        // set image source information for delayed image data loading
3700                        image.setImageSourceInfo(importer, series, resolution, adjRegion, t, z, channel);
3701                        // set image into the sequence
3702                        result.setImage(t - adjMinT, z - adjMinZ, image);
3703
3704                        progress += progressStep;
3705
3706                        // notify progress to loader frame
3707                        if (loadingFrame != null)
3708                            loadingFrame.setPosition(progress);
3709                    }
3710                }
3711            }
3712            finally
3713            {
3714                result.endUpdate();
3715            }
3716        }
3717
3718        return result;
3719    }
3720
3721    /**
3722     * <b>Internal use only !</b><br>
3723     * Load a single file and return result as Sequence list (for multi series).<br>
3724     * If <i>loadingFrame</i> is not <code>null</code> then it has 100 steps allocated to the
3725     * loading of current path.
3726     * 
3727     * @param importer
3728     *        Importer to use to open and load images (cannot be <code>null</code> here)
3729     * @param path
3730     *        image file to load
3731     * @param series
3732     *        Series index to load (for multi series sequence), set to 0 if unsure (default).<br>
3733     *        -1 is a special value so it gives a chance to the user to select series to open from a
3734     *        series selector dialog.
3735     * @param forceVolatile
3736     *        If set to <code>true</code> then image data is forced to volatile (see {@link IcyBufferedImage#isVolatile()}).<br>
3737     *        Note that if you don't have enough memory to load the whole Sequence into memory then image data are always made volatile.
3738     * @param groupSeries
3739     *        Enable series grouping into the same sequence (appended in T dimension), only meaningful if <code>series</code> = -1
3740     * @param loadingFrame
3741     *        the loading frame used to display progress of the operation (can be null)
3742     * @throws IOException
3743     */
3744    static List<Sequence> internalLoadSingle(SequenceFileImporter importer, String path, int series,
3745            boolean forceVolatile, boolean groupSeries, FileFrame loadingFrame)
3746            throws IOException, UnsupportedFormatException, OutOfMemoryError
3747    {
3748        final double endStep;
3749
3750        if (loadingFrame != null)
3751        {
3752            loadingFrame.setFilename(path);
3753            // 100 step reserved to load this image
3754            endStep = loadingFrame.getPosition() + 100d;
3755        }
3756        else
3757            endStep = 0d;
3758
3759        final List<Sequence> result = new ArrayList<Sequence>();
3760
3761        try
3762        {
3763            // prepare image loading for this file
3764            if (!importer.open(path, 0))
3765                throw new UnsupportedFormatException(
3766                        "Image file '" + path + "' is not supported by " + importer.toString() + " importer.");
3767
3768            // get metadata
3769            final OMEXMLMetadata meta = importer.getOMEXMLMetaData();
3770            // clean the metadata
3771            MetaDataUtil.clean(meta);
3772
3773            // series selection
3774            int selectedSeries[];
3775
3776            // give the opportunity to select the series(s) to open ?
3777            if (series == -1)
3778            {
3779                try
3780                {
3781                    // try to group series
3782                    if (groupSeries)
3783                        selectedSeries = groupSeries(meta);
3784                    // series selection (create a new importer instance as selectSerie(..) does async processes)
3785                    else
3786                        selectedSeries = selectSeries(cloneSequenceFileImporter(importer), path, meta, 0, false);
3787                }
3788                catch (Throwable t)
3789                {
3790                    IcyExceptionHandler.showErrorMessage(t, true, true);
3791                    System.err.print("Opening first series by default...");
3792                    selectedSeries = new int[] {0};
3793                }
3794
3795                // user cancelled action in the series selection ? null = cancel
3796                if (selectedSeries.length == 0)
3797                    return null;
3798            }
3799            else
3800                selectedSeries = new int[] {series};
3801
3802            // add sequence to result
3803            for (int s : selectedSeries)
3804            {
3805                final Sequence seq = internalLoadSingle(importer, meta, s, 0, null, -1, -1, -1, -1, -1, forceVolatile,
3806                        loadingFrame);
3807
3808                // group series together
3809                if ((result.size() > 0) && groupSeries)
3810                    concatenateSequence(result.get(0), seq, true, false);
3811                else
3812                    // just add to list
3813                    result.add(seq);
3814            }
3815
3816            // grouped series ?
3817            if (groupSeries && (selectedSeries.length > 1))
3818            {
3819                final Sequence seq = result.get(0);
3820                final String imageName = MetaDataUtil.getName(meta, 0);
3821
3822                // reset to metadata name
3823                if (StringUtil.isEmpty(imageName))
3824                    seq.setName(Sequence.DEFAULT_NAME + StringUtil.toString(seq.getId(), 3));
3825                else
3826                    seq.setName(imageName);
3827            }
3828
3829            // Don't close importer on success ! we want to keep it inside the sequence.
3830            // We will close it when finalizing the sequence...
3831            // importer.close();
3832        }
3833        catch (Throwable t)
3834        {
3835            // close importer when error happen (not stored in Sequence so we need to close it manually)
3836            importer.close();
3837
3838            // the importer is supposed to support this file --> re throw the exception
3839            if (importer.acceptFile(path))
3840                throw t;
3841        }
3842        finally
3843        {
3844            if (loadingFrame != null)
3845                loadingFrame.setPosition(endStep);
3846        }
3847
3848        return result;
3849    }
3850
3851    /**
3852     * <b>Internal use only !</b><br>
3853     * Load the specified image file group and return a single Sequence from it.<br>
3854     * As this method can take sometime, you should not call it from the EDT.<br>
3855     * 
3856     * @param group
3857     *        image path group we want to load
3858     * @param resolution
3859     *        Wanted resolution level for the image (use 0 if unsure), useful for large image<br>
3860     *        The retrieved image resolution is equal to <code>image.resolution / (2^resolution)</code><br>
3861     *        So for instance level 0 is the default/full image resolution while level 1 is base image resolution / 2
3862     *        and so on...
3863     * @param region
3864     *        The 2D region of the image we want to retrieve (in full image resolution).<br>
3865     *        If set to <code>null</code> then the whole XY plane of the image is returned.
3866     * @param minZ
3867     *        the minimum Z position of the image (slice) we want retrieve (inclusive).<br>
3868     *        Set to -1 to retrieve the whole stack.
3869     * @param maxZ
3870     *        the maximum Z position of the image (slice) we want retrieve (inclusive).<br>
3871     *        Set to -1 to retrieve the whole stack.
3872     * @param minT
3873     *        the minimum T position of the image (frame) we want retrieve (inclusive).<br>
3874     *        Set to -1 to retrieve the whole timelaps.
3875     * @param maxT
3876     *        the maximum T position of the image (frame) we want retrieve (inclusive).<br>
3877     *        Set to -1 to retrieve the whole timelaps.
3878     * @param channel
3879     *        C position of the image (channel) we want retrieve (-1 means all channel).
3880     * @param forceVolatile
3881     *        If set to <code>true</code> then image data is forced to volatile (see {@link IcyBufferedImage#isVolatile()}).<br>
3882     *        Note that if you don't have enough memory to load the whole Sequence into memory then image data are always made volatile.
3883     * @param directory
3884     *        specify is the source is a single complete directory
3885     * @param mainMenu
3886     *        menu object used to store recent file (can be null)
3887     * @param loadingFrame
3888     *        the loading frame used to cancel / display progress of the operation (can be null)
3889     * @Return the loaded Sequence (or <code>null<code> if loading was canceled)
3890     */
3891    static Sequence internalLoadGroup(SequenceFileGroup group, int resolution, Rectangle region, int minZ, int maxZ,
3892            int minT, int maxT, int channel, boolean forceVolatile, boolean directory, ApplicationMenu mainMenu,
3893            FileFrame loadingFrame) throws UnsupportedFormatException, IOException
3894    {
3895        final double endStep;
3896        Sequence result;
3897
3898        if (loadingFrame != null)
3899        {
3900            loadingFrame.setFilename(group.ident.base);
3901            loadingFrame.setAction("Loading image group");
3902            // 100 step reserved to load this image
3903            endStep = loadingFrame.getPosition() + 100d;
3904        }
3905        else
3906            endStep = 0d;
3907
3908        // use the special group importer
3909        final SequenceFileGroupImporter groupImporter = new SequenceFileGroupImporter();
3910
3911        try
3912        {
3913            // open image group (can't fail)
3914            groupImporter.open(group, 0);
3915
3916            // get metadata
3917            final OMEXMLMetadata meta = groupImporter.getOMEXMLMetaData();
3918            // clean the metadata
3919            MetaDataUtil.clean(meta);
3920
3921            result = internalLoadSingle(groupImporter, meta, 0, resolution, region, minZ, maxZ, minT, maxT, channel,
3922                    forceVolatile, loadingFrame);
3923
3924            // directory load ?
3925            if (directory)
3926            {
3927                // get directory without last separator
3928                final String fileDir = FileUtil.getDirectory(group.ident.base, false);
3929
3930                // set sequence name and filename to directory
3931                result.setName(FileUtil.getFileName(fileDir, false));
3932                result.setFilename(fileDir);
3933
3934                // add as one item to recent file list
3935                if (mainMenu != null)
3936                    mainMenu.addRecentFile(fileDir);
3937            }
3938            // normal file loading
3939            else
3940            {
3941                if (mainMenu != null)
3942                    mainMenu.addRecentFile(group.getPaths());
3943            }
3944
3945            // Don't close importer on success ! we want to keep it inside the sequence.
3946            // We will close it when finalizing the sequence...
3947            // groupImporter.close();
3948        }
3949        catch (Throwable t)
3950        {
3951            // close importer when error happen (not stored in Sequence so we need to close it manually)
3952            groupImporter.close();
3953
3954            // re throw
3955            throw t;
3956        }
3957        finally
3958        {
3959            if (loadingFrame != null)
3960                loadingFrame.setPosition(endStep);
3961        }
3962
3963        return result;
3964    }
3965
3966    /**
3967     * <b>Internal use only !</b><br>
3968     * Load the specified image file group and return a single Sequence from it.<br>
3969     * As this method can take sometime, you should not call it from the EDT.<br>
3970     * 
3971     * @param group
3972     *        image path group we want to load
3973     * @param directory
3974     *        specify is the source is a single complete directory
3975     * @param mainMenu
3976     *        menu object used to store recent file (can be null)
3977     * @param forceVolatile
3978     *        If set to <code>true</code> then image data is forced to volatile (see {@link IcyBufferedImage#isVolatile()}).<br>
3979     *        Note that if you don't have enough memory to load the whole Sequence into memory then image data are always made volatile.
3980     * @param loadingFrame
3981     *        the loading frame used to cancel / display progress of the operation (can be null)
3982     * @Return the loaded Sequence (or <code>null<code> if loading was canceled)
3983     */
3984    static Sequence internalLoadGroup(SequenceFileGroup group, boolean forceVolatile, boolean directory,
3985            ApplicationMenu mainMenu, FileFrame loadingFrame) throws UnsupportedFormatException, IOException
3986    {
3987        return internalLoadGroup(group, 0, null, -1, -1, -1, -1, -1, forceVolatile, directory, mainMenu, loadingFrame);
3988    }
3989
3990    public static String getSequenceName(Sequence sequence, String path, boolean multiSerie, int series)
3991    {
3992        // default name
3993        String name = FileUtil.getFileName(path, false);
3994
3995        // default name used --> use better name
3996        if (sequence.isDefaultName())
3997        {
3998            // multi series image --> add series info
3999            if (multiSerie)
4000                name += " - series " + StringUtil.toString(series);
4001        }
4002        else
4003        {
4004            // multi series image --> adjust name to keep file name info
4005            if (multiSerie)
4006                name += " - " + sequence.getName();
4007            else
4008                // just use sequence metadata name
4009                name = sequence.getName();
4010        }
4011
4012        return name;
4013    }
4014
4015    /**
4016     * Setup the specified sequence object given the different opening informations
4017     * 
4018     * @param sequence
4019     *        sequence to adjust properties
4020     * @param importer
4021     *        image path
4022     * @param multiSerie
4023     *        <code>true</code> if this Sequence comes from a multi series dataset
4024     * @param series
4025     *        series index
4026     * @param region
4027     *        Rectangle region we want to load from original image (full resolution)
4028     * @param resolution
4029     *        Resolution level to open
4030     * @param sizeZ
4031     *        original image sizeZ
4032     * @param sizeT
4033     *        original image sizeT
4034     * @param sizeC
4035     *        original image sizeC
4036     * @param minZ
4037     *        minimum Z slice wanted
4038     * @param maxZ
4039     *        maximum Z slice wanted
4040     * @param minT
4041     *        minimum T frame wanted
4042     * @param maxT
4043     *        maximum T frame wanted
4044     * @param channel
4045     *        channel we want to load (-1 for all)
4046     */
4047    public static void setupSequence(Sequence sequence, SequenceIdImporter importer, boolean multiSerie, int series,
4048            Rectangle region, int resolution, int sizeZ, int sizeT, int sizeC, int minZ, int maxZ, int minT, int maxT,
4049            int channel)
4050    {
4051        final String path = FileUtil.getGenericPath(importer.getOpened());
4052        // get default name
4053        String name = getSequenceName(sequence, path, multiSerie, series);
4054
4055        // original pixel size
4056        final double psx = sequence.getPixelSizeX();
4057        final double psy = sequence.getPixelSizeY();
4058        final double psz = sequence.getPixelSizeZ();
4059        // original position
4060        final double posX = sequence.getPositionX();
4061        final double posY = sequence.getPositionY();
4062        final double posZ = sequence.getPositionZ();
4063        // original time stamp (in ms)
4064        final long posT = sequence.getPositionT();
4065
4066        // get sequence metadata
4067        final OMEXMLMetadata metadata = sequence.getOMEXMLMetadata();
4068
4069        // cleanup planes
4070        for (int t = sizeT - 1; t >= 0; t--)
4071        {
4072            for (int z = sizeZ - 1; z >= 0; z--)
4073            {
4074                for (int c = 0; c < sizeC; c++)
4075                {
4076                    if ((t < minT) || (t > maxT) || (z < minZ) || (z > maxZ))
4077                        MetaDataUtil.removePlane(metadata, 0, maxT, maxZ, c);
4078                }
4079            }
4080        }
4081
4082        // single channel extraction ?
4083        if (channel != -1)
4084        {
4085            // adjust origin channel
4086            sequence.setOriginChannel(channel);
4087
4088            // clean channels and remaining planes
4089            for (int c = 0; c < sizeC; c++)
4090            {
4091                if (c != channel)
4092                {
4093                    MetaDataUtil.removePlanes(metadata, 0, -1, -1, c);
4094                    MetaDataUtil.removeChannel(metadata, 0, c);
4095                }
4096            }
4097        }
4098
4099        // adjust position X,Y
4100        if (region != null)
4101        {
4102            // set origin region
4103            sequence.setOriginXYRegion(region);
4104            // adjust position
4105            sequence.setPositionX(posX + (region.x * psx));
4106            sequence.setPositionY(posY + (region.y * psy));
4107        }
4108        // adjust position Z
4109        if (minZ > 0)
4110            sequence.setPositionZ(posZ + (minZ * psz));
4111        // adjust position T
4112        if (minT > 0)
4113            sequence.setTimeStamp(
4114                    posT + (long) (sequence.getPositionTOffset(minT, minZ, Math.max(0, channel)) * 1000d));
4115
4116        // using sub resolution ?
4117        if (resolution > 0)
4118        {
4119            final int divider = (int) Math.pow(2, resolution);
4120
4121            // adjust origin resolution
4122            sequence.setOriginResolution(resolution);
4123            // adjust pixel size
4124            sequence.setPixelSizeX(psx * divider);
4125            sequence.setPixelSizeY(psy * divider);
4126
4127            // adjust name
4128            name += " - binning=" + StringUtil.toString(divider);
4129        }
4130
4131        // adjust Z Range
4132        if ((minZ > 0) || (maxZ < (sizeZ - 1)))
4133        {
4134            sequence.setOriginZMin(minZ);
4135            sequence.setOriginZMax(maxZ);
4136
4137            // adjust name
4138            if (minZ == maxZ)
4139                name += " - Z" + StringUtil.toString(minZ);
4140            else
4141                name += " - Z[" + StringUtil.toString(minZ) + "-" + StringUtil.toString(maxZ) + "]";
4142        }
4143        // adjust T Range
4144        if ((minT > 0) || (maxT < (sizeT - 1)))
4145        {
4146            sequence.setOriginTMin(minT);
4147            sequence.setOriginTMax(maxT);
4148
4149            // adjust name
4150            if (minT == maxT)
4151                name += " - T" + StringUtil.toString(minT);
4152            else
4153                name += " - T[" + StringUtil.toString(minT) + "-" + StringUtil.toString(maxT) + "]";
4154        }
4155
4156        // need to adjust name for channel ?
4157        if (channel != -1)
4158            name += " - C" + StringUtil.toString(channel);
4159
4160        // set final name and filename
4161        sequence.setName(name);
4162        sequence.setFilename(path);
4163
4164        // set importer (for caching / delayed loading...)
4165        sequence.setImageProvider(importer);
4166    }
4167
4168    /**
4169     * Display the Series Selection frame for the given image and returns selected series(s).<br>
4170     * Returns a 0 length array if user canceled series selection.
4171     */
4172    public static int[] selectSeries(final SequenceIdImporter importer, final String path, final OMEXMLMetadata meta,
4173            int defaultSerie, final boolean singleSelection) throws UnsupportedFormatException, IOException
4174    {
4175        final int serieCount = MetaDataUtil.getNumSeries(meta);
4176        final int[] tmp = new int[serieCount + 1];
4177
4178        if (serieCount > 0)
4179        {
4180            tmp[0] = 1;
4181
4182            // multi series, display selection dialog
4183            if (serieCount > 1)
4184            {
4185                // allow user to select series to open
4186                if (!Icy.getMainInterface().isHeadLess())
4187                {
4188                    final Exception[] exception = new Exception[1];
4189                    exception[0] = null;
4190
4191                    // use invokeNow carefully !
4192                    ThreadUtil.invokeNow(new Runnable()
4193                    {
4194                        @Override
4195                        public void run()
4196                        {
4197                            try
4198                            {
4199                                final int[] series = new SeriesSelectionDialog(importer, path, meta, singleSelection)
4200                                        .getSelectedSeries();
4201                                // get result
4202                                tmp[0] = series.length;
4203                                System.arraycopy(series, 0, tmp, 1, series.length);
4204                            }
4205                            catch (Exception e)
4206                            {
4207                                exception[0] = e;
4208                            }
4209                        }
4210                    });
4211
4212                    // propagate exception
4213                    if (exception[0] instanceof UnsupportedFormatException)
4214                        throw (UnsupportedFormatException) exception[0];
4215                    else if (exception[0] instanceof IOException)
4216                        throw (IOException) exception[0];
4217                }
4218                // use the pre selected series
4219                else
4220                    tmp[1] = defaultSerie;
4221            }
4222            // only 1 series so open it
4223            else
4224                tmp[1] = 0;
4225        }
4226
4227        // copy back result to adjusted array
4228        final int[] result = new int[tmp[0]];
4229
4230        System.arraycopy(tmp, 1, result, 0, result.length);
4231
4232        return result;
4233    }
4234
4235    /**
4236     * Try to group series with similar images properties (XYZC dimension) starting from first image and return the list of grouped series index.
4237     */
4238    public static int[] groupSeries(OMEXMLMetadata meta)
4239    {
4240        final List<Integer> result = new ArrayList<Integer>();
4241
4242        final int sizeS = MetaDataUtil.getNumSeries(meta);
4243        if (sizeS > 0)
4244        {
4245            final int sizeX = MetaDataUtil.getSizeX(meta, 0);
4246            final int sizeY = MetaDataUtil.getSizeY(meta, 0);
4247            final int sizeZ = MetaDataUtil.getSizeZ(meta, 0);
4248            final int sizeC = MetaDataUtil.getSizeC(meta, 0);
4249            final DataType dataType = MetaDataUtil.getDataType(meta, 0);
4250
4251            result.add(Integer.valueOf(0));
4252
4253            // we can group series only if size T == 1 (as we group on T dimension)
4254            if (MetaDataUtil.getSizeT(meta, 0) == 1)
4255            {
4256                for (int s = 1; s < sizeS; s++)
4257                {
4258                    final int sx = MetaDataUtil.getSizeX(meta, s);
4259                    final int sy = MetaDataUtil.getSizeY(meta, s);
4260                    final int sz = MetaDataUtil.getSizeZ(meta, s);
4261                    final int sc = MetaDataUtil.getSizeC(meta, s);
4262                    final DataType dt = MetaDataUtil.getDataType(meta, s);
4263
4264                    if ((sx == sizeX) && (sy == sizeY) && (sz == sizeZ) && (sc == sizeC) && (dt == dataType)
4265                            && (MetaDataUtil.getSizeT(meta, s) == 1))
4266                        result.add(Integer.valueOf(s));
4267                }
4268            }
4269        }
4270
4271        final int[] ires = new int[result.size()];
4272
4273        for (int i = 0; i < ires.length; i++)
4274            ires[i] = result.get(i).intValue();
4275
4276        return ires;
4277    }
4278
4279    /**
4280     * Display the Series Selection frame for the given image and returns selected series(s).<br>
4281     * Returns a 0 length array if user canceled series selection.
4282     */
4283    public static int[] selectSeries(final SequenceFileImporter importer, final String path, final OMEXMLMetadata meta,
4284            int defaultSerie, final boolean singleSelection) throws UnsupportedFormatException, IOException
4285    {
4286        return selectSeries((SequenceIdImporter) importer, path, meta, defaultSerie, singleSelection);
4287    }
4288
4289    /**
4290     * Display the Series Selection frame for the given image and returns selected series(s).<br>
4291     * Returns a 0 length array if user canceled series selection.
4292     */
4293    public static int[] selectSeries(final SequenceFileImporter importer, final String path,
4294            final OMEXMLMetadataImpl meta, int defaultSerie, boolean singleSelection)
4295            throws UnsupportedFormatException, IOException
4296    {
4297        return selectSeries(importer, path, (OMEXMLMetadata) meta, defaultSerie, singleSelection);
4298    }
4299
4300    /**
4301     * Display the Series Selection frame for the given image and return the selected series (single selection).<br>
4302     * Returns <code>-1</code> if user canceled series selection.
4303     */
4304    public static int selectSerie(final SequenceFileImporter importer, final String path, final OMEXMLMetadata meta,
4305            int defaultSerie) throws UnsupportedFormatException, IOException
4306    {
4307        final int selected[] = selectSeries(importer, path, meta, defaultSerie, true);
4308
4309        if (selected.length > 0)
4310            return selected[0];
4311
4312        return -1;
4313    }
4314
4315    /**
4316     * @deprecated Use {@link #selectSerie(SequenceFileImporter, String, OMEXMLMetadata, int)} instead
4317     */
4318    @Deprecated
4319    public static int selectSerie(final SequenceFileImporter importer, final String path, final OMEXMLMetadataImpl meta,
4320            int defaultSerie) throws UnsupportedFormatException, IOException
4321    {
4322        return selectSerie(importer, path, (OMEXMLMetadata) meta, defaultSerie);
4323    }
4324
4325    static List<String> explode(List<String> paths)
4326    {
4327        return FileUtil.toPaths(FileUtil.explode(FileUtil.toFiles(paths), null, true, false));
4328    }
4329
4330    /**
4331     * Remove invalid image files from the list of files
4332     */
4333    public static List<String> cleanNonImageFile(List<String> paths)
4334    {
4335        final List<String> result = new ArrayList<String>();
4336
4337        // extensions based exclusion
4338        for (String path : paths)
4339        {
4340            // no image file or XML persistence --> ignore
4341            if (canDiscardImageFile(path))
4342                continue;
4343
4344            // XML file ?
4345            if (FileUtil.getFileExtension(path, false).toLowerCase().equals(XMLUtil.FILE_EXTENSION))
4346            {
4347                // ignore persistence files
4348                if (SequencePersistent.isValidXMLPersitence(path))
4349                    continue;
4350            }
4351
4352            result.add(path);
4353        }
4354
4355        return result;
4356    }
4357
4358    /**
4359     * @deprecated Use {@link SequenceFileSticher#groupAllFiles(SequenceFileImporter, Collection, boolean, FileFrame)} instead
4360     */
4361    @Deprecated
4362    public static List<FilePosition> getFilePositions(List<String> paths, boolean dimOrder, FileFrame loadingFrame)
4363    {
4364        final List<FilePosition> result = new ArrayList<FilePosition>(paths.size());
4365
4366        // use new path grouper method
4367        final Collection<SequenceFileGroup> groups = SequenceFileSticher.groupAllFiles(null, paths, dimOrder,
4368                loadingFrame);
4369
4370        // just build FilePosition from contained SequencePosition
4371        for (SequenceFileGroup group : groups)
4372        {
4373            final SequenceIdent ident = group.ident;
4374
4375            for (SequencePosition pos : group.positions)
4376                result.add(new FilePosition(pos.getPath(), ident.base, 0, pos.getIndexT(), pos.getIndexZ(),
4377                        pos.getIndexC()));
4378        }
4379
4380        return result;
4381    }
4382
4383    /**
4384     * @deprecated Use {@link SequenceFileSticher#groupAllFiles(SequenceFileImporter, Collection, boolean, FileFrame)} instead
4385     */
4386    @Deprecated
4387    public static List<FilePosition> getFilePositions(List<String> paths, boolean dimOrder)
4388    {
4389        return getFilePositions(paths, dimOrder, null);
4390    }
4391}