/* * Copyright 2010-2013 Institut Pasteur. * * This file is part of Icy. * * Icy is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Icy is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Icy. If not, see <http://www.gnu.org/licenses/>. */ package icy.file; import icy.common.exception.UnsupportedFormatException; import icy.gui.dialog.ImporterSelectionDialog; import icy.gui.dialog.SeriesSelectionDialog; import icy.gui.frame.progress.FailedAnnounceFrame; import icy.gui.frame.progress.FileFrame; import icy.gui.menu.ApplicationMenu; import icy.image.ChannelPosition; import icy.image.IcyBufferedImage; import icy.image.ImagePosition; import icy.image.ImageProvider; import icy.main.Icy; import icy.plugin.PluginDescriptor; import icy.plugin.PluginLauncher; import icy.plugin.PluginLoader; import icy.preferences.GeneralPreferences; import icy.sequence.DimensionId; import icy.sequence.MetaDataUtil; import icy.sequence.Sequence; import icy.sequence.SequenceIdImporter; import icy.sequence.SequenceImporter; import icy.system.IcyExceptionHandler; import icy.system.thread.ThreadUtil; import icy.type.collection.CollectionUtil; import icy.util.OMEUtil; import icy.util.StringUtil; import icy.util.StringUtil.AlphanumComparator; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.TreeMap; import loci.formats.FormatException; import loci.formats.IFormatReader; import loci.formats.ImageReader; import loci.formats.ome.OMEXMLMetadataImpl; /** * Sequence / Image loader class. * * @author Fabrice de Chaumont & Stephane */ public class Loader { private static class PositionChunk { /** Depth (Z) dimension prefixes (taken from Bio-Formats for almost) */ static final String[] prefixesZ = {"fp", "sec", "z", "zs", "focal", "focalplane"}; /** Time (T) dimension prefixes (taken from Bio-Formats for almost) */ static final String[] prefixesT = {"t", "tl", "tp", "time"}; /** Channel (C) dimension prefixes (taken from Bio-Formats for almost) */ static final String[] prefixesC = {"c", "ch", "b", "band", "w", "wl", "wave", "wavelength"}; /** Serie (S)dimension prefixes (taken from Bio-Formats for almost) */ static final String[] prefixesS = {"s", "series", "sp"}; public DimensionId dim; public int value; PositionChunk(String prefix, int value) { super(); dim = null; if (!StringUtil.isEmpty(prefix)) { final String prefixLC = prefix.toLowerCase(); dim = getDim(prefixLC, prefixesZ, DimensionId.Z); if (dim == null) dim = getDim(prefixLC, prefixesT, DimensionId.T); if (dim == null) dim = getDim(prefixLC, prefixesC, DimensionId.C); if (dim == null) dim = getDim(prefixLC, prefixesS, DimensionId.NULL); } this.value = value; } private static DimensionId getDim(String prefix, String prefixes[], DimensionId d) { for (String suffix : prefixes) if (prefix.endsWith(suffix)) return d; return null; } } private static class Position { final List<PositionChunk> chunks; final String baseName; Position(String baseName) { super(); this.baseName = baseName; chunks = new ArrayList<Loader.PositionChunk>(); } void addChunk(String prefix, int value) { final PositionChunk chunk = new PositionChunk(prefix, value); // get the previous chunk for this dimension final PositionChunk previousChunk = getChunk(chunk.dim, false); // already have a chunk for this dimension --> remove it if (previousChunk != null) removeChunk(previousChunk); // add the chunk chunks.add(chunk); } int getValue(DimensionId dim) { final PositionChunk chunk = getChunk(dim, true); if (chunk != null) return chunk.value; // not found return -1; } public boolean isUnknowDim(DimensionId dim) { return getChunk(dim, false) == null; } boolean removeChunk(DimensionId dim) { return removeChunk(getChunk(dim, true)); } private boolean removeChunk(PositionChunk chunk) { return chunks.remove(chunk); } PositionChunk getChunk(DimensionId dim, boolean allowUnknown) { if (dim != null) { for (PositionChunk chunk : chunks) if (chunk.dim == dim) return chunk; if (allowUnknown) return getChunkFromUnknown(dim); } return null; } private PositionChunk getChunkFromUnknown(DimensionId dim) { final boolean hasCChunk = (getChunk(DimensionId.C, false) != null); final boolean hasZChunk = (getChunk(DimensionId.Z, false) != null); final boolean hasTChunk = (getChunk(DimensionId.T, false) != null); final int unknownCount = getUnknownChunkCount(); // priority order : T, Z, C switch (dim) { case C: if (hasCChunk) return null; if (hasTChunk) { if (hasZChunk) { // T and Z chunk present --> C = unknown[0] if (unknownCount >= 1) return getUnknownChunk(0); } else { // T chunk present --> Z = unknown[0]; C = unknown[1] if (unknownCount >= 2) return getUnknownChunk(1); } } else if (hasZChunk) { // Z chunk present --> T = unknown[0]; C = unknown[1] if (unknownCount >= 2) return getUnknownChunk(1); } else { // no other chunk present --> T = unknown[0]; Z = unknown[1]; C = unknown[2] if (unknownCount >= 3) return getUnknownChunk(2); } break; case Z: if (hasZChunk) return null; if (hasTChunk) { // T chunk present --> Z = unknown[0] if (unknownCount >= 1) return getUnknownChunk(0); } else { // T chunk not present --> T = unknown[0]; Z = unknown[1] if (unknownCount >= 2) return getUnknownChunk(1); } break; case T: if (hasTChunk) return null; // T = unknown[0] if (unknownCount >= 1) return getUnknownChunk(0); break; } return null; } private PositionChunk getUnknownChunk(int i) { int ind = 0; for (PositionChunk chunk : chunks) { if (chunk.dim == null) { if (ind == i) return chunk; ind++; } } return null; } int getUnknownChunkCount() { int result = 0; for (PositionChunk chunk : chunks) if (chunk.dim == null) result++; return result; } @Override public String toString() { return "Position [S:" + getValue(DimensionId.NULL) + " T:" + getValue(DimensionId.T) + " Z:" + getValue(DimensionId.Z) + " C:" + getValue(DimensionId.C) + "]"; } } public static class FilePosition extends ChannelPosition { public final String path; public String basePath; int s; public FilePosition(String path, String basePath, int s, int t, int z, int c) { super(t, z, c); this.s = s; this.path = path; this.basePath = basePath; } /** * @deprecated Use {@link FilePosition#FilePosition(String, String, int, int, int, int)} * instead. */ @Deprecated public FilePosition(String path, int t, int z, int c) { super(t, z, c); this.path = path; basePath = ""; } public FilePosition(String path) { super(); this.path = path; } public FilePosition(FilePosition fp) { this(fp.path, fp.basePath, fp.s, fp.t, fp.z, fp.c); } public int getS() { return s; } public void setS(int s) { this.s = s; } public void set(int s, int t, int z, int c) { super.set(t, z, c); this.s = s; } @Override public int compareTo(ImagePosition o) { if (o instanceof FilePosition) { int result = basePath.compareTo(((FilePosition) o).basePath); if (result != 0) return result; final int sp = ((FilePosition) o).s; if (s > sp) return 1; if (s < sp) return -1; } return super.compareTo(o); } @Override public String toString() { return "File=" + path + " Position=[T:" + t + " Z:" + z + " C:" + c + "]"; } } private final static String nonImageExtensions[] = {"xml", "txt", "pdf", "xls", "doc", "docx", "pdf", "rtf", "exe", "wav", "mp3", "app"}; /** * Returns all available resource importer. */ public static List<Importer> getImporters() { final List<PluginDescriptor> plugins = PluginLoader.getPlugins(Importer.class); final List<Importer> result = new ArrayList<Importer>(); for (PluginDescriptor plugin : plugins) { try { // add the importer result.add((Importer) PluginLauncher.startSafe(plugin)); } catch (Exception e) { // show a message in the output console IcyExceptionHandler.showErrorMessage(e, false, true); // and send an error report (silent as we don't want a dialog appearing here) IcyExceptionHandler.report(plugin, IcyExceptionHandler.getErrorMessage(e, true)); } } return result; } /** * Returns all available resource (non image) importer which take file as input. */ public static List<FileImporter> getFileImporters() { final List<PluginDescriptor> plugins = PluginLoader.getPlugins(FileImporter.class); final List<FileImporter> result = new ArrayList<FileImporter>(); for (PluginDescriptor plugin : plugins) { try { // add the importer result.add((FileImporter) PluginLauncher.startSafe(plugin)); } catch (Exception e) { // show a message in the output console IcyExceptionHandler.showErrorMessage(e, false, true); // and send an error report (silent as we don't want a dialog appearing here) IcyExceptionHandler.report(plugin, IcyExceptionHandler.getErrorMessage(e, true)); } } return result; } /** * Returns a Map containing the appropriate file importer for the specified file.<br> * A file can be absent from the returned Map when no importer support it.<br> * * @param importers * the base list of importer we want to test to open file. * @param paths * the list of file we want to retrieve importer for. * @param useFirstFound * if set to <code>true</code> then the first matching importer is automatically selected * otherwise a dialog appears to let the user to choose the correct importer when * severals importers match for a file. */ public static Map<FileImporter, List<String>> getFileImporters(List<FileImporter> importers, List<String> paths, boolean useFirstFound) { final Map<FileImporter, List<String>> result = new HashMap<FileImporter, List<String>>(importers.size()); final Map<String, FileImporter> extensionImporters = new HashMap<String, FileImporter>(importers.size()); for (String path : paths) { final String ext = FileUtil.getFileExtension(path, false); FileImporter imp; // try to get importer from extension first imp = extensionImporters.get(ext); // do not exist yet if (imp == null) { // find it imp = getFileImporter(importers, path, useFirstFound); // set the importer for this extension if (imp != null) extensionImporters.put(ext, imp); } // importer found for this path ? if (imp != null) { // retrieve current list of path for this importer List<String> list = result.get(imp); // do not exist yet --> create it if (list == null) { list = new ArrayList<String>(); // set the list for this importer result.put(imp, list); } // add path to the list list.add(path); } } return result; } /** * Returns a Map containing the appropriate file importer for the specified file.<br> * A file can be absent from the returned Map when no importer support it.<br> * * @param paths * the list of file we want to retrieve importer for. * @param useFirstFound * if set to <code>true</code> then the first matching importer is automatically selected * otherwise a dialog appears to let the user to choose the correct importer when * severals importers match for a file. */ public static Map<FileImporter, List<String>> getFileImporters(List<String> paths, boolean useFirstFound) { return getFileImporters(getFileImporters(), paths, useFirstFound); } /** * Returns all file importer which can open the specified file. */ public static List<FileImporter> getFileImporters(List<FileImporter> importers, String path) { final List<FileImporter> result = new ArrayList<FileImporter>(importers.size()); for (FileImporter importer : importers) if (importer.acceptFile(path)) result.add(importer); return result; } /** * Returns all file importer which can open the specified file. */ public static List<FileImporter> getFileImporters(String path) { return getFileImporters(getFileImporters(), path); } /** * Returns the appropriate file importer for the specified file.<br> * Returns <code>null</code> if no importer can open the file. * * @param importers * the base list of importer we want to test to open file. * @param path * the file we want to retrieve importer for. * @param useFirstFound * if set to <code>true</code> then the first matching importer is automatically selected * otherwise a dialog appears to let the user to choose the correct importer when * severals importers match. * @see #getFileImporters(List, String) */ public static FileImporter getFileImporter(List<FileImporter> importers, String path, boolean useFirstFound) { final List<FileImporter> result = new ArrayList<FileImporter>(importers.size()); for (FileImporter importer : importers) { if (importer.acceptFile(path)) { if (useFirstFound) return importer; result.add(importer); } } // let user select the good importer return selectFileImporter(result, path); } /** * Returns the appropriate file importer for the specified file.<br> * Returns <code>null</code> if no importer can open the file. * * @param path * the file we want to retrieve importer for. * @param useFirstFound * if set to <code>true</code> then the first matching importer is automatically selected * otherwise a dialog appears to let the user to choose the correct importer when * severals importers match. * @see #getFileImporters(String) */ public static FileImporter getFileImporter(String path, boolean useFirstFound) { return getFileImporter(getFileImporters(), path, useFirstFound); } /** * Display a dialog to let the user select the appropriate file importer for the specified file. */ public static FileImporter selectFileImporter(final List<FileImporter> importers, final String path) { if (importers.size() == 0) return null; if (importers.size() == 1) return importers.get(0); if (Icy.getMainInterface().isHeadLess()) return importers.get(0); final Object result[] = new Object[1]; // use invokeNow carefully ! ThreadUtil.invokeNow(new Runnable() { @Override public void run() { // get importer result[0] = new ImporterSelectionDialog(importers, path).getSelectedImporter(); } }); return (FileImporter) result[0]; } /** * Returns all available sequence importer (different from {@link SequenceIdImporter}). */ public static List<SequenceImporter> getSequenceImporters() { final List<PluginDescriptor> plugins = PluginLoader.getPlugins(SequenceImporter.class); final List<SequenceImporter> result = new ArrayList<SequenceImporter>(); for (PluginDescriptor plugin : plugins) { try { // add the importer result.add((SequenceImporter) PluginLauncher.startSafe(plugin)); } catch (Exception e) { // show a message in the output console IcyExceptionHandler.showErrorMessage(e, false, true); // and send an error report (silent as we don't want a dialog appearing here) IcyExceptionHandler.report(plugin, IcyExceptionHandler.getErrorMessage(e, true)); } } return result; } /** * Returns all available sequence importer which take id as input. */ public static List<SequenceIdImporter> getSequenceIdImporters() { final List<PluginDescriptor> plugins = PluginLoader.getPlugins(SequenceIdImporter.class); final List<SequenceIdImporter> result = new ArrayList<SequenceIdImporter>(); for (PluginDescriptor plugin : plugins) { try { // add the importer result.add((SequenceIdImporter) PluginLauncher.startSafe(plugin)); } catch (Exception e) { // show a message in the output console IcyExceptionHandler.showErrorMessage(e, false, true); // and send an error report (silent as we don't want a dialog appearing here) IcyExceptionHandler.report(plugin, IcyExceptionHandler.getErrorMessage(e, true)); } } return result; } /** * Returns all available sequence importer which take file as input. */ public static List<SequenceFileImporter> getSequenceFileImporters() { final List<PluginDescriptor> plugins = PluginLoader.getPlugins(SequenceFileImporter.class); final List<SequenceFileImporter> result = new ArrayList<SequenceFileImporter>(); for (PluginDescriptor plugin : plugins) { try { // add the importer result.add((SequenceFileImporter) PluginLauncher.startSafe(plugin)); } catch (Exception e) { // show a message in the output console IcyExceptionHandler.showErrorMessage(e, false, true); // and send an error report (silent as we don't want a dialog appearing here) IcyExceptionHandler.report(plugin, IcyExceptionHandler.getErrorMessage(e, true)); } } return result; } /** * Returns a Map containing the appropriate sequence file importer for the specified files.<br> * A file can be absent from the returned Map when no importer support it.<br> * * @param importers * the base list of importer we want to test to open file. * @param paths * the list of file we want to retrieve importer for. * @param useFirstFound * if set to <code>true</code> then the first matching importer is automatically selected * otherwise a dialog appears to let the user to choose the correct importer when * severals importers match for a file. */ public static Map<SequenceFileImporter, List<String>> getSequenceFileImporters( List<SequenceFileImporter> importers, List<String> paths, boolean useFirstFound) { final Map<SequenceFileImporter, List<String>> result = new HashMap<SequenceFileImporter, List<String>>( importers.size()); final Map<String, SequenceFileImporter> extensionImporters = new HashMap<String, SequenceFileImporter>( importers.size()); for (String path : paths) { final String ext = FileUtil.getFileExtension(path, false); SequenceFileImporter imp; // try to get importer from extension first imp = extensionImporters.get(ext); // do not exist yet if (imp == null) { // find it imp = getSequenceFileImporter(importers, path, useFirstFound); // set the importer for this extension if (imp != null) extensionImporters.put(ext, imp); } // importer found for this path ? if (imp != null) { // retrieve current list of path for this importer List<String> list = result.get(imp); // do not exist yet --> create it if (list == null) { list = new ArrayList<String>(); // set the list for this importer result.put(imp, list); } // add path to the list list.add(path); } } return result; } /** * Returns a Map containing the appropriate sequence file importer for the specified file.<br> * A file can be absent from the returned Map when no importer support it.<br> * * @param paths * the list of file we want to retrieve importer for. * @param useFirstFound * if set to <code>true</code> then the first matching importer is automatically selected * otherwise a dialog appears to let the user to choose the correct importer when * severals importers match for a file. */ public static Map<SequenceFileImporter, List<String>> getSequenceFileImporters(List<String> paths, boolean useFirstFound) { return getSequenceFileImporters(getSequenceFileImporters(), paths, useFirstFound); } /** * Returns all sequence file importer which can open the specified file. */ public static List<SequenceFileImporter> getSequenceFileImporters(List<SequenceFileImporter> importers, String path) { final List<SequenceFileImporter> result = new ArrayList<SequenceFileImporter>(importers.size()); for (SequenceFileImporter importer : importers) if (importer.acceptFile(path)) result.add(importer); return result; } /** * Returns all sequence file importer which can open the specified file. */ public static List<SequenceFileImporter> getSequenceFileImporters(String path) { return getSequenceFileImporters(getSequenceFileImporters(), path); } /** * Returns the appropriate sequence file importer for the specified file.<br> * Depending the parameters it will open a dialog to let the user choose the importer to use * when severals match.<br> * Returns <code>null</code> if no importer can open the file. * * @param importers * the base list of importer we want to test to open file. * @param path * the file we want to retrieve importer for. * @param useFirstFound * if set to <code>true</code> then the first matching importer is automatically selected * otherwise a dialog appears to let the user to choose the correct importer when * severals importers match. * @see #getSequenceFileImporters(List, String) */ public static SequenceFileImporter getSequenceFileImporter(List<SequenceFileImporter> importers, String path, boolean useFirstFound) { final List<SequenceFileImporter> result = new ArrayList<SequenceFileImporter>(importers.size()); for (SequenceFileImporter importer : importers) { if (importer.acceptFile(path)) { if (useFirstFound) return importer; result.add(importer); } } // let user select the good importer return selectSequenceFileImporter(result, path); } /** * Returns the appropriate sequence file importer for the specified file.<br> * Depending the parameters it will open a dialog to let the user choose the importer to use * when severals match.<br> * Returns <code>null</code> if no importer can open the file. * * @param path * the file we want to retrieve importer for. * @param useFirstFound * if set to <code>true</code> then the first matching importer is automatically selected * otherwise a dialog appears to let the user to choose the correct importer when * severals importers match. * @see #getSequenceFileImporters(String) */ public static SequenceFileImporter getSequenceFileImporter(String path, boolean useFirstFound) { return getSequenceFileImporter(getSequenceFileImporters(), path, useFirstFound); } /** * @deprecated Use {@link #getSequenceFileImporter(List, String, boolean)} */ @Deprecated public static SequenceFileImporter getSequenceFileImporter(List<SequenceFileImporter> importers, String path) { return getSequenceFileImporter(importers, path, true); } /** * @deprecated Use {@link #getSequenceFileImporter(String, boolean)} */ @Deprecated public static SequenceFileImporter getSequenceFileImporter(String path) { return getSequenceFileImporter(path, true); } /** * Display a dialog to let the user select the appropriate sequence file importer for the * specified file. */ public static SequenceFileImporter selectSequenceFileImporter(final List<SequenceFileImporter> importers, final String path) { if (importers.size() == 0) return null; if (importers.size() == 1) return importers.get(0); if (Icy.getMainInterface().isHeadLess()) return importers.get(0); final Object result[] = new Object[1]; // use invokeNow carefully ! ThreadUtil.invokeNow(new Runnable() { @Override public void run() { // get importer final ImporterSelectionDialog selectionDialog = new ImporterSelectionDialog(importers, path); if (!selectionDialog.isCanceled()) result[0] = selectionDialog.getSelectedImporter(); else result[0] = null; } }); return (SequenceFileImporter) result[0]; } /** * Returns <code>true</code> if the specified path describes a file type which is well known to * not be an image file.<br> * For instance <i>.exe</i>, <i>.wav</i> or <i>.doc</i> file cannot specify an image file so we * can quickly discard them (extension based exclusion) */ public static boolean canDiscardImageFile(String path) { final String ext = FileUtil.getFileExtension(path, false).toLowerCase(); for (String rejected : nonImageExtensions) if (ext.equals(rejected)) return true; return false; } /** * Returns true if the specified file is a supported image file. */ public static boolean isSupportedImageFile(String path) { return (getSequenceFileImporters(path).size() > 0); } /** * @deprecated Use {@link #isSupportedImageFile(String)} instead. */ @Deprecated public static boolean isImageFile(String path) { return isSupportedImageFile(path); } /** * Returns path which are supported by the specified imported for the given list of paths. */ public static List<String> getSupportedFiles(SequenceFileImporter importer, List<String> paths) { final List<String> result = new ArrayList<String>(); for (String path : paths) { if (importer.acceptFile(path)) result.add(path); } return result; } /** * @deprecated Use {@link #getSequenceFileImporters(String)} instead. */ @Deprecated public static IFormatReader getReader(String path) throws FormatException, IOException { return new ImageReader().getReader(path); } /** * @deprecated Use {@link #getMetaData(File)} instead. */ @Deprecated protected static OMEXMLMetadataImpl getMetaData(IFormatReader reader, String path) throws FormatException, IOException { // prepare meta data store structure reader.setMetadataStore(new OMEXMLMetadataImpl()); // load file with LOCI library reader.setId(path); return (OMEXMLMetadataImpl) reader.getMetadataStore(); } /** * Loads and returns metadata of the specified image file with given importer.<br> * It can returns <code>null</code> if the specified file is not a valid or supported) image * file. */ public static OMEXMLMetadataImpl getMetaData(SequenceFileImporter importer, String path) throws UnsupportedFormatException, IOException { if (importer.open(path, 0)) { try { return importer.getMetaData(); } finally { importer.close(); } } return null; } /** * Loads and returns metadata of the specified image file. */ public static OMEXMLMetadataImpl getMetaData(String path) throws UnsupportedFormatException, IOException { OMEXMLMetadataImpl result; UnsupportedFormatException lastError = null; for (SequenceFileImporter importer : getSequenceFileImporters(path)) { try { result = getMetaData(importer, path); if (result != null) return result; } catch (UnsupportedFormatException e) { lastError = e; } } throw new UnsupportedFormatException("Image file '" + path + "' is not supported :\n", lastError); } /** * @deprecated Use {@link #getMetaData(String)} instead. */ @Deprecated public static OMEXMLMetadataImpl getMetaData(File file) throws UnsupportedFormatException, IOException { return getMetaData(file.getAbsolutePath()); } // /** // * Use the given importer to load and return metadata of the specified image file. // * // * @throws UnsupportedFormatException // * @throws IOException // */ // public static OMEXMLMetadataImpl getMetaData(SequenceFileImporter importer, File file) // throws UnsupportedFormatException, IOException // { // // load current file and add to results // return importer.getMetaData(file); // } /** * Returns a thumbnail of the specified image file path.<br> * It can return <code>null</code> if the specified file is not a valid or supported image file. * * @param importer * Importer used to open and load the thumbnail from the image file. * @param path * image file path. * @param serie * Serie index we want to retrieve thumbnail from (for multi serie image).<br> * Set to 0 if unsure. */ public static IcyBufferedImage loadThumbnail(SequenceFileImporter importer, String path, int serie) throws UnsupportedFormatException, IOException { if (importer.open(path, 0)) { try { return importer.getThumbnail(serie); } finally { importer.close(); } } return null; } /** * Returns a thumbnail of the specified image file path. * * @param path * image file path. * @param serie * Serie index we want to retrieve thumbnail from (for multi serie image).<br> * Set to 0 if unsure. */ public static IcyBufferedImage loadThumbnail(String path, int serie) throws UnsupportedFormatException, IOException { IcyBufferedImage result; UnsupportedFormatException lastError = null; for (SequenceFileImporter importer : getSequenceFileImporters(path)) { try { result = loadThumbnail(importer, path, serie); if (result != null) return result; } catch (UnsupportedFormatException e) { lastError = e; } } throw new UnsupportedFormatException("Image file '" + path + "' is not supported :\n", lastError); } /** * @deprecated Use {@link IcyBufferedImage#createFrom(IFormatReader, int, int)} instead. */ @Deprecated public static IcyBufferedImage loadImage(IFormatReader reader, int z, int t) throws FormatException, IOException { // return an icy image return IcyBufferedImage.createFrom(reader, z, t); } /** * @deprecated Use {@link IcyBufferedImage#createFrom(IFormatReader, int, int)} with Z and T * parameters set to 0. */ @Deprecated public static IcyBufferedImage loadImage(IFormatReader reader) throws FormatException, IOException { // return an icy image return IcyBufferedImage.createFrom(reader, 0, 0); } /** * @deprecated Use {@link #loadImage(String, int, int)} instead. */ @Deprecated public static IcyBufferedImage loadImage(File file, int z, int t) throws FormatException, IOException { return loadImage(file.getAbsolutePath(), z, t); } /** * @deprecated Use {@link #loadImage(String)} instead. */ @Deprecated public static IcyBufferedImage loadImage(File file) throws UnsupportedFormatException, IOException { return loadImage(file.getAbsolutePath()); } /** * @deprecated Use {@link #loadImage(String, int, int, int)} instead. */ @Deprecated public static IcyBufferedImage loadImage(String path, int z, int t) throws FormatException, IOException { final IFormatReader reader = getReader(path); // disable file grouping reader.setGroupFiles(false); // set file id reader.setId(path); try { // return an icy image return IcyBufferedImage.createFrom(reader, z, t); } finally { // close reader reader.close(); } } /** * Load and return the image at given position from the specified file path.<br> * For lower image level access, you can use importer methods. * * @param importer * Importer used to open and load the image file.<br> * @param path * image file path. * @param serie * Serie index we want to retrieve image from (for multi serie image).<br> * Set to 0 if unsure (default). * @param z * Z position of the image to open. * @param t * T position of the image to open. * @throws IOException * @throws UnsupportedFormatException */ public static IcyBufferedImage loadImage(SequenceFileImporter importer, String path, int serie, int z, int t) throws UnsupportedFormatException, IOException { if ((importer == null) || !importer.open(path, 0)) throw new UnsupportedFormatException("Image file '" + path + "' is not supported !"); try { return importer.getImage(serie, z, t); } finally { importer.close(); } } /** * Load and return the image at given position from the specified file path.<br> * For lower image level access, you can use {@link #getSequenceFileImporter(String, boolean)} * method and * directly work through the returned {@link ImageProvider} interface. * * @param path * image file path. * @param serie * Serie index we want to retrieve image from (for multi serie image).<br> * Set to 0 if unsure (default). * @param z * Z position of the image to open. * @param t * T position of the image to open. * @throws IOException * @throws UnsupportedFormatException */ public static IcyBufferedImage loadImage(String path, int serie, int z, int t) throws UnsupportedFormatException, IOException { return loadImage(getSequenceFileImporter(path, true), path, serie, z, t); } /** * Load and return a single image from the specified file path.<br> * If the specified file contains severals image the first image is returned. */ public static IcyBufferedImage loadImage(String path) throws UnsupportedFormatException, IOException { return loadImage(path, 0, 0, 0); } /** * @deprecated Use {@link #loadSequences(File[], int, boolean, boolean, boolean)} instead. */ @Deprecated public static Sequence[] loadSequences(File[] files, int[] series, boolean separate, boolean autoOrder, boolean showProgress) { final List<Sequence> result = new ArrayList<Sequence>(); final List<String> paths = FileUtil.toPaths(CollectionUtil.asList(files)); if (series == null) result.addAll(loadSequences(paths, -1, separate, autoOrder, false, showProgress)); else { for (int serie : series) result.addAll(loadSequences(paths, serie, separate, autoOrder, false, showProgress)); } return result.toArray(new Sequence[result.size()]); } /** * @deprecated Use {@link #loadSequences(File[], int[], boolean, boolean, boolean)} instead. */ @Deprecated public static List<Sequence> loadSequences(List<File> files, List<Integer> series, boolean separate, boolean autoOrder, boolean showProgress) { final int[] seriesArray; if (series != null) { seriesArray = new int[series.size()]; for (int i = 0; i < seriesArray.length; i++) seriesArray[i] = series.get(i).intValue(); } else { seriesArray = new int[1]; seriesArray[0] = 0; } return Arrays.asList(loadSequences(files.toArray(new File[files.size()]), seriesArray, separate, autoOrder, showProgress)); } /** * @deprecated Use {@link #loadSequences(File[], int[], boolean, boolean, boolean)} instead. */ @Deprecated public static List<Sequence> loadSequences(List<File> files, List<Integer> series, boolean separate, boolean showProgress) { return loadSequences(files, series, separate, true, showProgress); } /** * @deprecated Use {@link #loadSequences(File[], int[], boolean, boolean, boolean)} instead. */ @Deprecated public static List<Sequence> loadSequences(List<File> files, List<Integer> series, boolean separate) { return loadSequences(files, series, separate, true, true); } /** * @deprecated Use {@link #loadSequences(File[], int[], boolean, boolean, boolean)} instead. */ @Deprecated public static List<Sequence> loadSequences(List<File> files, List<Integer> series) { return loadSequences(files, series, false, true, true); } /** * @deprecated Use {@link #loadSequences(File[], int[], boolean, boolean, boolean)} instead. */ @Deprecated public static List<Sequence> loadSequences(List<File> files, boolean separate, boolean showProgress) { return loadSequences(files, null, separate, true, showProgress); } /** * @deprecated Use {@link #loadSequences(File[], int[], boolean, boolean, boolean)} instead. */ @Deprecated public static List<Sequence> loadSequences(List<File> files, boolean separate) { return loadSequences(files, null, separate, true, true); } /** * @deprecated Use {@link #loadSequences(File[], int[], boolean, boolean, boolean)} instead. */ @Deprecated public static List<Sequence> loadSequences(List<File> files, boolean separate, boolean display, boolean addToRecent) { return loadSequences(files, null, separate, true, true); } /** * @deprecated Use {@link #loadSequence(File, int, boolean)} instead. */ @Deprecated public static Sequence[] loadSequences(File file, int[] series, boolean showProgress) { return loadSequences(new File[] {file}, series, false, true, showProgress); } /** * @deprecated Use {@link #loadSequences(File, int[], boolean)} instead. */ @Deprecated public static List<Sequence> loadSequences(File file, List<Integer> series, boolean showProgress) { final int[] seriesArray; if (series != null) { seriesArray = new int[series.size()]; for (int i = 0; i < seriesArray.length; i++) seriesArray[i] = series.get(i).intValue(); } else { seriesArray = new int[1]; seriesArray[0] = 0; } return Arrays.asList(loadSequences(new File[] {file}, seriesArray, false, true, showProgress)); } /** * @deprecated Use {@link #loadSequences(File, int[], boolean)} instead. */ @Deprecated public static List<Sequence> loadSequences(File file, List<Integer> series) { return loadSequences(file, series, true); } /** * @deprecated Use {@link #loadSequences(File, int[], boolean)} instead. */ @Deprecated public static List<Sequence> loadSequences(File file, List<Integer> series, boolean display, boolean addToRecent) { return loadSequences(file, series, true); } /** * @deprecated Use {@link #loadSequence(File[], int, boolean)} instead. */ @Deprecated public static Sequence loadSequence(List<File> files, boolean showProgress) { return loadSequence(files.toArray(new File[files.size()]), -1, showProgress); } /** * @deprecated Use {@link #loadSequence(File[], int, boolean)} instead. */ @Deprecated public static Sequence loadSequence(List<File> files) { return loadSequence(files.toArray(new File[files.size()]), -1, true); } /** * @deprecated Use {@link #loadSequence(File[], int, boolean)} instead or * {@link #load(File, boolean)} if you want to display the resulting sequence. */ @Deprecated public static Sequence loadSequence(List<File> files, boolean display, boolean addToRecent) { return loadSequence(files.toArray(new File[files.size()]), -1, true); } /** * @deprecated Use {@link #loadSequence(File, int, boolean)} instead. */ @Deprecated public static Sequence loadSequence(File file, boolean showProgress) { return loadSequence(new File[] {file}, -1, showProgress); } /** * @deprecated Use {@link #loadSequence(File, int, boolean)} instead. */ @Deprecated public static Sequence loadSequence(File file) { return loadSequence(new File[] {file}, -1, true); } /** * @deprecated Use {@link #loadSequences(List, int, boolean, boolean, boolean, boolean)} * instead. */ @Deprecated public static Sequence[] loadSequences(File[] files, int serie, boolean separate, boolean autoOrder, boolean showProgress) { final List<Sequence> result = loadSequences(FileUtil.toPaths(CollectionUtil.asList(files)), serie, separate, autoOrder, false, showProgress); return result.toArray(new Sequence[result.size()]); } /** * Load a sequence from the specified list of file and returns it.<br> * As the function can take sometime you should not call it from the AWT EDT.<br> * The function can return null if no sequence can be loaded from the specified files. * * @param files * List of image file to load. * @param serie * Serie index to load (for multi serie sequence), set to 0 if unsure (default). * @param showProgress * Show progression of loading process. */ @Deprecated public static Sequence loadSequence(File[] files, int serie, boolean showProgress) { final Sequence[] result = loadSequences(files, serie, false, true, showProgress); if (result.length > 0) return result[0]; return null; } /** * @deprecated Use {@link #loadSequence(String, int, boolean)} instead. */ @Deprecated public static Sequence loadSequence(File file, int serie, boolean showProgress) { return loadSequence(new File[] {file}, serie, showProgress); } /** * Load a list of sequence from the specified list of file with the given * {@link SequenceFileImporter} and returns them.<br> * As the function can take sometime you should not call it from the AWT EDT.<br> * The method returns an empty array if an error occurred or if no file could be opened (not * supported).<br> * If the user cancelled the action (serie selection dialog) then it returns <code>null</code>. * * @param importer * Importer used to open and load image files.<br> * If set to <code>null</code> the loader will search for a compatible importer and if * several importers match the user will have to select the appropriate one from a * selection dialog. * @param paths * List of image file to load. * @param serie * Serie index to load (for multi serie sequence), set to 0 if unsure (default).<br> * -1 is a special value so it gives a chance to the user<br> * to select the serie to open from a serie selector dialog. * @param separate * Force image to be loaded in separate sequence. * @param autoOrder * Try to order image in sequence from their filename * @param addToRecent * If set to true the files list will be traced in recent opened sequence. * @param showProgress * Show progression of loading process. */ public static List<Sequence> loadSequences(SequenceFileImporter importer, List<String> paths, int serie, boolean separate, boolean autoOrder, boolean addToRecent, boolean showProgress) { final List<Sequence> result = new ArrayList<Sequence>(); // detect if this is a complete folder load final boolean directory = (paths.size() == 1) && new File(paths.get(0)).isDirectory(); // explode path list final List<String> singlePaths = explodeAndClean(paths); // get the sequence importer first final Map<SequenceFileImporter, List<String>> sequenceFileImporters; // importer not defined --> find the appropriate importers if (importer == null) sequenceFileImporters = getSequenceFileImporters(singlePaths, false); else { sequenceFileImporters = new HashMap<SequenceFileImporter, List<String>>(1); sequenceFileImporters.put(importer, new ArrayList<String>(singlePaths)); } for (Entry<SequenceFileImporter, List<String>> entry : sequenceFileImporters.entrySet()) { final SequenceFileImporter imp = entry.getKey(); final List<String> currPaths = entry.getValue(); final boolean dir = directory && (sequenceFileImporters.size() == 1) && (currPaths.size() == singlePaths.size()); // load sequence result.addAll(loadSequences(imp, currPaths, serie, separate, autoOrder, dir, addToRecent, showProgress)); // remove loaded files singlePaths.removeAll(currPaths); } // remaining files ? if (singlePaths.size() > 0) { // get first found importer for remaining files final Map<SequenceFileImporter, List<String>> importers = getSequenceFileImporters(singlePaths, true); // user canceled action for these paths so we remove them for (List<String> values : importers.values()) singlePaths.removeAll(values); if (singlePaths.size() > 0) { // just log in console System.err.println("No compatible importer found for the following files:"); for (String path : singlePaths) System.err.println(path); System.err.println(); } } // return sequences return result; } /** * Loads a list of sequence from the specified list of file and returns them.<br> * As the function can take sometime you should not call it from the AWT EDT.<br> * The method returns an empty array if an error occurred or if no file could not be opened (not * supported).<br> * If several importers match to open a file the user will have to select the appropriate one * from a selection dialog. * * @param paths * List of image file to load. * @param serie * Serie index to load (for multi serie sequence), set to 0 if unsure (default).<br> * -1 is a special value so it gives a chance to the user<br> * to select the serie to open from a serie selector dialog. * @param separate * Force image to be loaded in separate sequence. * @param autoOrder * Try to order image in sequence from their filename * @param addToRecent * If set to true the files list will be traced in recent opened sequence. * @param showProgress * Show progression of loading process. */ public static List<Sequence> loadSequences(List<String> paths, int serie, boolean separate, boolean autoOrder, boolean addToRecent, boolean showProgress) { return loadSequences(null, paths, serie, separate, autoOrder, addToRecent, showProgress); } /** * Load a sequence from the specified list of file and returns it.<br> * As the function can take sometime you should not call it from the AWT EDT.<br> * The function can return null if no sequence can be loaded from the specified files. * * @param importer * Importer used to load the image file.<br> * If set to <code>null</code> the loader will search for a compatible importer and if * several importers match the user will have to select the appropriate one from a * selection dialog. * @param paths * List of image file to load. * @param serie * Serie index to load (for multi serie sequence), set to 0 if unsure (default). * @param showProgress * Show progression of loading process. * @see #getSequenceFileImporter(String, boolean) */ public static Sequence loadSequence(SequenceFileImporter importer, List<String> paths, int serie, boolean showProgress) { final List<Sequence> result = loadSequences(importer, paths, serie, false, true, false, showProgress); if (result.size() > 0) return result.get(0); return null; } /** * Load a sequence from the specified list of file and returns it.<br> * As the function can take sometime you should not call it from the AWT EDT.<br> * The function can return null if no sequence can be loaded from the specified files.<br> * If several importers match to open a file the user will have to select the appropriate one * from a selection dialog. * * @param paths * List of image file to load. * @param serie * Serie index to load (for multi serie sequence), set to 0 if unsure (default). * @param showProgress * Show progression of loading process. * @see #getSequenceFileImporter(String, boolean) */ public static Sequence loadSequence(List<String> paths, int serie, boolean showProgress) { return loadSequence(null, paths, serie, showProgress); } /** * Load a sequence from the specified file.<br> * As the function can take sometime you should not call it from the AWT EDT. * * @param importer * Importer used to load the image file.<br> * If set to <code>null</code> the loader will search for a compatible importer and if * several importers match the user will have to select the appropriate one from a * selection dialog. * @param path * Image file to load. * @param serie * Serie index to load (for multi serie sequence), set to 0 if unsure (default). * @param showProgress * Show progression of loading process. */ public static Sequence loadSequence(SequenceFileImporter importer, String path, int serie, boolean showProgress) { return loadSequence(importer, CollectionUtil.createArrayList(path), serie, showProgress); } /** * Load a sequence from the specified file.<br> * As the function can take sometime you should not call it from the AWT EDT.<br> * If several importers match to open the file the user will have to select the appropriate one * from a selection dialog. * * @param path * Image file to load. * @param serie * Serie index to load (for multi serie sequence), set to 0 if unsure (default). * @param showProgress * Show progression of loading process. */ public static Sequence loadSequence(String path, int serie, boolean showProgress) { return loadSequence(null, path, serie, showProgress); } /** * @deprecated Use {@link #load(List, boolean, boolean, boolean)} instead. */ @Deprecated public static void load(List<File> files) { load(files.toArray(new File[files.size()]), false, true, true); } /** * @deprecated Use {@link #load(List, boolean, boolean, boolean)} instead. */ @Deprecated public static void load(List<File> files, boolean separate) { load(files.toArray(new File[files.size()]), separate, true, true); } /** * @deprecated Use {@link #load(List, boolean, boolean, boolean)} instead. */ @Deprecated public static void load(List<File> files, boolean separate, boolean showProgress) { load(files.toArray(new File[files.size()]), separate, true, showProgress); } // /** // * @deprecated Use {@link #load(File[], boolean, boolean, boolean)} instead. // */ // @Deprecated // public static void load(List<File> files, boolean separate, boolean autoOrder, boolean // showProgress) // { // load(files.toArray(new File[files.size()]), separate, autoOrder, showProgress); // } /** * @deprecated Use {@link #load(String, boolean)} instead. */ @Deprecated public static void load(File file) { load(new File[] {file}, false, false, true); } /** * @deprecated Use {@link #load(List, boolean, boolean, boolean)} instead. */ @Deprecated public static void load(final File[] files, final boolean separate, final boolean autoOrder, final boolean showProgress) { // asynchronous call ThreadUtil.bgRun(new Runnable() { @Override public void run() { // load sequence final Sequence[] sequences = loadSequences(files, -1, separate, autoOrder, showProgress); // and display them for (Sequence seq : sequences) Icy.getMainInterface().addSequence(seq); } }); } /** * @deprecated Use {@link #load(String, boolean)} instead. */ @Deprecated public static void load(File file, boolean showProgress) { load(new File[] {file}, false, false, showProgress); } /** * Load the specified files with the given {@link FileImporter}.<br> * The loading process is asynchronous.<br> * The FileImporter is responsible to make the loaded files available in the application.<br> * This method should be used only for non image file. * * @param importer * Importer used to open and load files.<br> * If set to <code>null</code> the loader will search for a compatible importer and if * several importers match the user will have to select the appropriate one from a * selection dialog. * @param paths * list of file to load * @param showProgress * Show progression in loading process */ public static void load(final FileImporter importer, final List<String> paths, final boolean showProgress) { // asynchronous call ThreadUtil.bgRun(new Runnable() { @Override public void run() { // explode path list final List<String> singlePaths = explodeAndClean(paths); if (singlePaths.size() > 0) { // get the file importer now for remaining file final Map<FileImporter, List<String>> fileImporters; // importer not defined --> find the appropriate importers if (importer == null) fileImporters = getFileImporters(singlePaths, false); else { fileImporters = new HashMap<FileImporter, List<String>>(1); fileImporters.put(importer, new ArrayList<String>(singlePaths)); } for (Entry<FileImporter, List<String>> entry : fileImporters.entrySet()) { final FileImporter importer = entry.getKey(); final List<String> currPaths = entry.getValue(); // load files loadFiles(importer, paths, true, showProgress); // remove loaded files singlePaths.removeAll(currPaths); } } // remaining files ? if (singlePaths.size() > 0) { // get first found importer for remaining files final Map<SequenceFileImporter, List<String>> importers = getSequenceFileImporters(singlePaths, true); // user canceled action for these paths so we remove them for (List<String> values : importers.values()) singlePaths.removeAll(values); if (singlePaths.size() > 0) { // just log in console System.err.println("No compatible importer found for the following files:"); for (String path : singlePaths) System.err.println(path); System.err.println(); } } } }); } /** * Load the specified files with the given {@link FileImporter}.<br> * The FileImporter is responsible to make the loaded files available in the application.<br> * This method should be used only for non image file. * * @param importer * Importer used to open and load image files. * @param paths * list of file to load * @param addToRecent * If set to true the files list will be traced in recent opened files. * @param showProgress * Show progression in loading process */ static void loadFiles(FileImporter importer, List<String> paths, boolean addToRecent, boolean showProgress) { final ApplicationMenu mainMenu; final FileFrame loadingFrame; if (addToRecent) mainMenu = Icy.getMainInterface().getApplicationMenu(); else mainMenu = null; if (showProgress && !Icy.getMainInterface().isHeadLess()) { loadingFrame = new FileFrame("Loading", null); loadingFrame.setLength(paths.size()); loadingFrame.setPosition(0); } else loadingFrame = null; try { // load each file in a separate sequence for (String path : paths) { if (loadingFrame != null) loadingFrame.incPosition(); // load current file importer.load(path, loadingFrame); // add as separate item to recent file list if (mainMenu != null) mainMenu.addRecentLoadedFile(new File(path)); } } catch (Throwable t) { // just show the error IcyExceptionHandler.showErrorMessage(t, true); if (loadingFrame != null) new FailedAnnounceFrame("Failed to open file(s), see the console output for more details."); } finally { if (loadingFrame != null) loadingFrame.close(); } } /** * Load the specified image files with the given {@link SequenceFileImporter}.<br> * The loading process is asynchronous.<br> * If <i>separate</i> is false the loader try to set image in the same sequence.<br> * If <i>separate</i> is true each image is loaded in a separate sequence.<br> * The resulting sequences are automatically displayed when the process complete. * * @param importer * Importer used to open and load image files.<br> * If set to <code>null</code> the loader will search for a compatible importer and if * several importers match the user will have to select the appropriate one from a * selection dialog. * @param paths * list of image file to load * @param separate * Force image to be loaded in separate sequence * @param autoOrder * Try to order image in sequence from their filename * @param showProgress * Show progression in loading process */ public static void load(final SequenceFileImporter importer, final List<String> paths, final boolean separate, final boolean autoOrder, final boolean showProgress) { // asynchronous call ThreadUtil.bgRun(new Runnable() { @Override public void run() { // load sequence final List<Sequence> sequences = loadSequences(importer, paths, -1, separate, autoOrder, true, showProgress); // and display them for (Sequence seq : sequences) Icy.getMainInterface().addSequence(seq); } }); } /** * Load the specified files (asynchronous process) by using automatically the appropriate * {@link FileImporter} or {@link SequenceFileImporter}. If several importers match to open the * file the user will have to select the appropriate one from a selection dialog.<br> * <br> * If the specified files are image files:<br> * When <i>separate</i> is <code>false</code> the loader try to set image in the same sequence.<br> * When <i>separate</i> is <code>true</code> each image is loaded in a separate sequence.<br> * The resulting sequences are automatically displayed when the process complete. * * @param paths * list of file to load * @param separate * Force image to be loaded in separate sequence (image files only) * @param autoOrder * Try to order image in sequence from their filename (image files only) * @param showProgress * Show progression in loading process */ public static void load(final List<String> paths, final boolean separate, final boolean autoOrder, final boolean showProgress) { // asynchronous call ThreadUtil.bgRun(new Runnable() { @Override public void run() { // detect if this is a complete folder load final boolean directory = (paths.size() == 1) && new File(paths.get(0)).isDirectory(); // explode path list final List<String> singlePaths = explodeAndClean(paths); // get the sequence importer first final Map<SequenceFileImporter, List<String>> sequenceFileImporters = getSequenceFileImporters( singlePaths, false); for (Entry<SequenceFileImporter, List<String>> entry : sequenceFileImporters.entrySet()) { final SequenceFileImporter importer = entry.getKey(); final List<String> currPaths = entry.getValue(); final boolean dir = directory && (sequenceFileImporters.size() == 1) && (currPaths.size() == singlePaths.size()); // load sequence final List<Sequence> sequences = loadSequences(importer, currPaths, -1, separate, autoOrder, dir, true, showProgress); // and display them for (Sequence seq : sequences) Icy.getMainInterface().addSequence(seq); // remove loaded files singlePaths.removeAll(currPaths); } if (singlePaths.size() > 0) { // get the file importer now for remaining file final Map<FileImporter, List<String>> fileImporters = getFileImporters(singlePaths, false); for (Entry<FileImporter, List<String>> entry : fileImporters.entrySet()) { final FileImporter importer = entry.getKey(); final List<String> currPaths = entry.getValue(); // load files loadFiles(importer, paths, true, showProgress); // remove loaded files singlePaths.removeAll(currPaths); } } // remaining files ? if (singlePaths.size() > 0) { // get first found importer for remaining files final Map<SequenceFileImporter, List<String>> importers = getSequenceFileImporters(singlePaths, true); // user canceled action for these paths so we remove them for (List<String> values : importers.values()) singlePaths.removeAll(values); if (singlePaths.size() > 0) { // just log in console System.err.println("No compatible importer found for the following files:"); for (String path : singlePaths) System.err.println(path); System.err.println(); } } } }); } /** * Load the specified file (asynchronous process) by using automatically the appropriate * {@link FileImporter} or {@link SequenceFileImporter}. If several importers match to open the * file the user will have to select the appropriate one from a selection dialog.<br> * <br> * If the specified file is an image file, the resulting sequence is automatically displayed * when process complete. * * @param path * file to load * @param showProgress * Show progression of loading process. */ public static void load(String path, boolean showProgress) { load(CollectionUtil.createArrayList(path), false, false, showProgress); } /** * @deprecated Use {@link #loadSequences(List, int, boolean, boolean, boolean, boolean)} * instead. */ @Deprecated static Sequence[] loadSequences(SequenceFileImporter importer, File[] files, int serie, boolean separate, boolean autoOrder, boolean directory, boolean addToRecent, boolean showProgress) { final List<String> paths = CollectionUtil.asList(FileUtil.toPaths(files)); final List<Sequence> result = loadSequences(importer, paths, serie, separate, autoOrder, directory, addToRecent, showProgress); return result.toArray(new Sequence[result.size()]); } /** * Loads the specified image files and return them as list of sequence.<br> * If 'separate' is false the loader try to set images in the same sequence.<br> * If separate is true each image is loaded in a separate sequence.<br> * As this method can take sometime, you should not call it from the EDT.<br> * * @param importer * Importer used to open and load images. * @param paths * list of image file to load * @param serie * Serie index to load (for multi serie sequence), set to 0 if unsure (default).<br> * -1 is a special value so it gives a chance to the user to select series to open from a * serie selector dialog. * @param separate * Force image to be loaded in separate sequence * @param autoOrder * If set to true then images are automatically orderer from their filename. * @param directory * Specify is the source is a single complete directory * @param addToRecent * If set to true the files list will be traced in recent opened sequence. * @param showProgress * Show progression in loading process */ static List<Sequence> loadSequences(SequenceFileImporter importer, List<String> paths, int serie, boolean separate, boolean autoOrder, boolean directory, boolean addToRecent, boolean showProgress) { final List<Sequence> result = new ArrayList<Sequence>(); // nothing to load if (paths.size() <= 0) return result; final ApplicationMenu mainMenu; final FileFrame loadingFrame; if (addToRecent) mainMenu = Icy.getMainInterface().getApplicationMenu(); else mainMenu = null; if (showProgress && !Icy.getMainInterface().isHeadLess()) loadingFrame = new FileFrame("Loading", null); else loadingFrame = null; try { final List<String> remainingFiles = new ArrayList<String>(paths); final List<SequenceFileImporter> importers; // used the specified importer if any if (importer != null) importers = CollectionUtil.createArrayList(importer); else importers = getSequenceFileImporters(); if (separate) { if (loadingFrame != null) { // each file can contains several image so we use 100 "inter step" loadingFrame.setLength(paths.size() * 100d); loadingFrame.setPosition(0d); } // load each file in a separate sequence for (String path : paths) { // load the file final List<Sequence> sequences = internalLoadSingle(importers, path, serie, loadingFrame); // special case where loading was interrupted --> exit if (sequences == null) return result; else if (sequences.size() > 0) { // add sequences to result result.addAll(sequences); // remove path from remaining remainingFiles.remove(path); // add as separate item to recent file list if (mainMenu != null) mainMenu.addRecentLoadedFile(new File(path)); } } } else { final TreeMap<Integer, Sequence> map = new TreeMap<Integer, Sequence>(); if (loadingFrame != null) loadingFrame.setAction("Extracting position from filename"); final List<FilePosition> filePositions = getFilePositions(paths, autoOrder); int lastS = 0; if (loadingFrame != null) { loadingFrame.setAction("Loading"); // each file can contains several image so we use 100 "inter step" loadingFrame.setLength(filePositions.size() * 100d); loadingFrame.setPosition(0d); } // load each file in a separate sequence for (FilePosition filePos : filePositions) { final String path = filePos.path; // load the file final List<Sequence> sequences = internalLoadSingle(importers, path, serie, loadingFrame); // special case where loading was interrupted --> exit if (sequences == null) return result; final int s = filePos.getS(); final int z = filePos.getZ(); final int t = filePos.getT(); final int c = filePos.getC(); boolean concat; // special case of single result --> try to concatenate to last sequence if ((sequences.size() == 1) && !map.isEmpty()) { final Sequence seq = sequences.get(0); final int sizeZ = seq.getSizeZ(); final int sizeT = seq.getSizeT(); final int sizeC = seq.getSizeC(); concat = true; // concatenation restriction if (lastS != s) concat = false; if ((sizeZ > 1) && (z > 0)) concat = false; if ((sizeT > 1) && (t > 0)) concat = false; if ((sizeC > 1) && (c > 0)) concat = false; if (concat) { // find last sequence for this channel final Sequence lastSequence = map.get(Integer.valueOf(c)); // determine if concatenation is possible if ((lastSequence != null) && !lastSequence.isCompatible(seq.getFirstImage())) concat = false; } // update serie index lastS = s; } else concat = false; // sequence correctly loaded ? if (sequences.size() > 0) { if (concat) { final Sequence seq = sequences.get(0); // find last sequence for this channel Sequence lastSequence = map.get(Integer.valueOf(c)); // concatenate lastSequence = concatenateSequence(lastSequence, seq, t > 0, z > 0); // store the merged sequence for this channel map.put(Integer.valueOf(c), lastSequence); } else { // concatenate sequences in map and add it to result list addSequences(result, map); // if on first channel then put the last sequence result in the map if (c == 0) map.put(Integer.valueOf(0), sequences.remove(sequences.size() - 1)); // and add the rest to the list if (sequences.size() > 0) result.addAll(sequences); } // remove path from remaining remainingFiles.remove(path); } } // concatenate last sequences in map and add it to result list addSequences(result, map); // add as one item to recent file list if (mainMenu != null) { // set only the directory entry if (directory) mainMenu.addRecentFile(FileUtil.getDirectory(paths.get(0), false)); else mainMenu.addRecentFile(paths); } } if (remainingFiles.size() > 0) { System.err.println("Cannot open the following file(s) (format not supported):"); for (String path : remainingFiles) System.err.println(path); if (loadingFrame != null) { new FailedAnnounceFrame( "Some file(s) could not be opened (format not supported). See the console output for more details."); } } // directory load fit in a single sequence ? if ((result.size() == 1) && directory) { final Sequence seq = result.get(0); // get directory without last separator final String fileDir = FileUtil.getDirectory(paths.get(0), false); // set sequence name and filename to directory seq.setName(FileUtil.getFileName(fileDir, false)); seq.setFilename(fileDir); } // TODO: restore colormap --> try to recover colormap // load sequence XML data if (GeneralPreferences.getSequencePersistence()) { for (Sequence seq : result) seq.loadXMLData(); } } catch (Throwable t) { // just show the error IcyExceptionHandler.showErrorMessage(t, true); if (loadingFrame != null) new FailedAnnounceFrame("Failed to open file(s), see the console output for more details."); } finally { if (loadingFrame != null) loadingFrame.close(); } return result; } /** * Concatenate the <i>src</i> sequence to the <i>dest</i> one. */ static Sequence concatenateSequence(Sequence dest, Sequence src, boolean onT, boolean onZ) { if (dest == null) return src; if (src == null) return dest; final int dst; final int dsz; final int sst = src.getSizeT(); final int ssz = src.getSizeZ(); if (onT) { if (onZ) { dst = dest.getSizeT() - 1; dsz = dest.getSizeZ(dst); } else { dst = dest.getSizeT(); dsz = 0; } } else { dst = 0; dsz = onZ ? dest.getSizeZ() : 0; } // put 'dest' in update state to avoid useless recalculations if (!dest.isUpdating()) dest.beginUpdate(); for (int t = 0; t < sst; t++) for (int z = 0; z < ssz; z++) dest.setImage(t + dst, z + dsz, src.getImage(t, z)); return dest; } static void addSequences(List<Sequence> result, TreeMap<Integer, Sequence> map) { if (!map.isEmpty()) { // get all sequence from the map orderer by channel final Collection<Sequence> sequencesC = map.values(); // remove update state for (Sequence seq : sequencesC) if (seq.isUpdating()) seq.endUpdate(); // final Sequence sequences[] = sequencesC.toArray(new Sequence[sequencesC.size()]); // // // several sequences ? // if (sequences.length > 1) // { // // concatenate sequences on C dimension // final Sequence merged = SequenceUtil.concatC(sequences); // // then add the result to the list // result.add(merged); // } // else // result.add(sequences[0]); // better to not merge the C channel after all result.addAll(sequencesC); // clear the map map.clear(); } } /** * Internal load a single file and return result as Sequence list (for multi serie).<br> * If <i>loadingFrame</i> is not <code>null</code> then it has 100 steps allocated to the * loading of * current path. * * @throws IOException */ static List<Sequence> internalLoadSingle(List<SequenceFileImporter> importers, String path, int serie, FileFrame loadingFrame) throws IOException { final double endStep; if (loadingFrame != null) { loadingFrame.setFilename(path); // 100 step reserved to load this image endStep = loadingFrame.getPosition() + 100d; } else endStep = 0d; final List<Sequence> result = new ArrayList<Sequence>(); for (SequenceFileImporter importer : importers) { try { // prepare image loading for this file if (!importer.open(path, 0)) throw new UnsupportedFormatException("Image file '" + path + "' is not supported !"); // get metadata final OMEXMLMetadataImpl meta = importer.getMetaData(); // clean the metadata MetaDataUtil.clean(meta); // get number of serie final int serieCount = MetaDataUtil.getNumSerie(meta); int selectedSeries[]; try { // do serie selection (need to create a new instance of the importer as // selectSerie(..) does async processes) selectedSeries = selectSerie(importer.getClass().newInstance(), path, meta, serie, serieCount); } catch (Exception e) { IcyExceptionHandler.showErrorMessage(e, true, true); System.err.print("Open first serie by default..."); selectedSeries = new int[] {0}; } // user cancelled action in the serie selection ? null = cancel if (selectedSeries.length == 0) return null; for (int s : selectedSeries) { final Sequence seq = createNewSequence(path, meta, s, serieCount > 1); final int sizeZ = MetaDataUtil.getSizeZ(meta, s); final int sizeT = MetaDataUtil.getSizeT(meta, s); // set local length for loader frame final int numImage = sizeZ * sizeT * selectedSeries.length; final double progressStep = 100d / numImage; double progress = 0d; if (loadingFrame != null) progress = loadingFrame.getPosition(); seq.beginUpdate(); try { for (int t = 0; t < sizeT; t++) { for (int z = 0; z < sizeZ; z++) { // cancel requested ? --> return null to inform about cancel if ((loadingFrame != null) && loadingFrame.isCancelRequested()) return null; // load image and add it to the sequence seq.setImage(t, z, importer.getImage(s, z, t)); progress += progressStep; // notify progress to loader frame if (loadingFrame != null) loadingFrame.setPosition(progress); } } } finally { seq.endUpdate(); } // add sequence to result result.add(seq); } // no need to test with others importer break; } catch (UnsupportedFormatException e) { // the importer should support this file ? if (importer.acceptFile(path)) // display the error in console and pass to next importer IcyExceptionHandler.showErrorMessage(e, false); } finally { // close importer importer.close(); if (loadingFrame != null) loadingFrame.setPosition(endStep); } } return result; } static Sequence createNewSequence(String path, OMEXMLMetadataImpl meta, int serie, boolean multiSerie) { // create a new sequence final Sequence result = new Sequence(OMEUtil.createOMEMetadata(meta, serie)); // default name final String name = FileUtil.getFileName(path, false); // default name used --> use better name if (result.isDefaultName()) { // multi series image --> add serie info if (multiSerie) result.setName(name + " - serie " + StringUtil.toString(serie)); else result.setName(name); } else { // multi series image --> adjust name to keep file name info if (multiSerie) result.setName(name + " - " + result.getName()); } // set final filename result.setFilename(path); return result; } static int[] selectSerie(final SequenceFileImporter importer, final String path, final OMEXMLMetadataImpl meta, int defaultSerie, int serieCount) throws UnsupportedFormatException, IOException { final int[] tmp = new int[serieCount + 1]; if (serieCount > 0) { tmp[0] = 1; // multi serie, display selection dialog if (serieCount > 1) { // allow user to select series to open if ((defaultSerie == -1) && !Icy.getMainInterface().isHeadLess()) { final Exception[] exception = new Exception[1]; exception[0] = null; // use invokeNow carefully ! ThreadUtil.invokeNow(new Runnable() { @Override public void run() { try { final int[] series = new SeriesSelectionDialog(importer, path, meta) .getSelectedSeries(); // get result tmp[0] = series.length; System.arraycopy(series, 0, tmp, 1, series.length); } catch (Exception e) { exception[0] = e; } } }); // propagate exception if (exception[0] instanceof UnsupportedFormatException) throw (UnsupportedFormatException) exception[0]; else if (exception[0] instanceof IOException) throw (IOException) exception[0]; } // use the pre selected serie else tmp[1] = (defaultSerie != -1) ? defaultSerie : 0; } // only 1 serie so open it else tmp[1] = 0; } // copy back result to adjusted array final int[] result = new int[tmp[0]]; System.arraycopy(tmp, 1, result, 0, result.length); return result; } /** * @deprecated Use {@link #explodeAndClean(List)} instead. */ @Deprecated static File[] explodeAndClean(File[] files) { final File[] allFiles = FileUtil.explode(files, null, true, false); final List<File> result = new ArrayList<File>(); // extensions based exclusion for (int i = 0; i < allFiles.length; i++) { final File file = allFiles[i]; // keep non discarded images if (!canDiscardImageFile(file.getPath())) result.add(file); } return result.toArray(new File[result.size()]); } static List<String> explodeAndClean(List<String> paths) { final List<String> allPaths = FileUtil.toPaths(FileUtil.explode(FileUtil.toFiles(paths), null, true, false)); final List<String> result = new ArrayList<String>(); // extensions based exclusion for (String path : allPaths) { // keep non discarded images if (!canDiscardImageFile(path)) result.add(path); } return result; } /** * Sort the specified image files from their name and return their corresponding Sequence * position information.<br> * * @param paths * image files we want to sort * @param dimOrder * if true we try to determine the Z, T and C image position as well else * only simple T ordering is done. */ public static List<FilePosition> getFilePositions(List<String> paths, boolean dimOrder) { final List<String> filenames = new ArrayList<String>(paths); final List<Position> positions = new ArrayList<Position>(paths.size()); final List<FilePosition> result = new ArrayList<FilePosition>(paths.size()); // smart sort on name Collections.sort(filenames, new AlphanumComparator()); if (dimOrder) { // build position for each file for (String filename : filenames) positions.add(getPosition(filename)); final Set<String> baseNames = new HashSet<String>(); for (Position position : positions) baseNames.add(position.baseName); for (String baseName : baseNames) { // remove fixed dimension while (cleanPositions(baseName, positions, DimensionId.NULL)) ; while (cleanPositions(baseName, positions, DimensionId.T)) ; while (cleanPositions(baseName, positions, DimensionId.Z)) ; while (cleanPositions(baseName, positions, DimensionId.C)) ; } boolean tSet = false; boolean tCanChange = true; boolean zSet = false; boolean zCanChange = true; for (Position position : positions) { if (position.getValue(DimensionId.T) != -1) tSet = true; if (position.getValue(DimensionId.Z) != -1) zSet = true; if (!position.isUnknowDim(DimensionId.T)) tCanChange = false; if (!position.isUnknowDim(DimensionId.Z)) zCanChange = false; } // Z and T position are not fixed, try to open 1 image to get its size information if (tCanChange && zCanChange && (filenames.size() > 0)) { try { final OMEXMLMetadataImpl metadata = getMetaData(filenames.get(0)); final boolean tMulti = MetaDataUtil.getSizeT(metadata, 0) > 1; final boolean zMulti = MetaDataUtil.getSizeZ(metadata, 0) > 1; boolean swapZT = false; if (tMulti ^ zMulti) { // multi T but single Z if (tMulti) { // T position set but can be swapped with Z if (tSet && tCanChange && !zSet) swapZT = true; } else // multi Z but single T { // Z position set but can be swapped with T if (zSet && zCanChange && !tSet) swapZT = true; } } // swat T and Z dimension if (swapZT) { for (Position position : positions) { final PositionChunk zChunk = position.getChunk(DimensionId.Z, true); final PositionChunk tChunk = position.getChunk(DimensionId.T, true); // swap dim if (zChunk != null) zChunk.dim = DimensionId.T; if (tChunk != null) tChunk.dim = DimensionId.Z; } } } catch (Exception e) { // ignore... } } // create FilePosition result array for (int i = 0; i < positions.size(); i++) { final Position pos = positions.get(i); result.add(new FilePosition(filenames.get(i), pos.baseName, pos.getValue(DimensionId.NULL), pos .getValue(DimensionId.T), pos.getValue(DimensionId.Z), pos.getValue(DimensionId.C))); } // sort it on basePath, S, T, Z, C position Collections.sort(result); } else { // create FilePosition result array int i = 0; for (String filename : filenames) result.add(new FilePosition(filename, getBaseName(filename), 0, i++, 0, 0)); } // compact indexes if (result.size() > 0) { FilePosition pos, lastPos; int s, t, z, c; pos = result.get(0); // keep trace of last position lastPos = new FilePosition(pos); s = 0; t = 0; z = 0; c = 0; // set start position pos.set(s, t, z, c); for (int i = 1; i < result.size(); i++) { pos = result.get(i); // base path changed if (!StringUtil.equals(pos.basePath, lastPos.basePath)) { s++; t = 0; z = 0; c = 0; } // S position changed else if (pos.getS() != lastPos.getS()) { s++; t = 0; z = 0; c = 0; } // T position changed else if (pos.getT() != lastPos.getT()) { t++; z = 0; c = 0; } // Z position changed else if (pos.getZ() != lastPos.getZ()) { z++; c = 0; } // C position changed else if (pos.getC() != lastPos.getC()) c++; // else assume T changed else t++; // keep trace of last position lastPos = new FilePosition(pos); // update current position pos.set(s, t, z, c); } } return result; } private static String getBaseName(String text) { String result = new String(text); int pos = 0; while (pos < result.length()) { final int st = StringUtil.getNextDigitCharIndex(result, pos); if (st != -1) { // get ending digit char index int end = StringUtil.getNextNonDigitCharIndex(result, st); if (end < 0) end = result.length(); final int size = end - st; // remove number from name if number size < 6 if (size < 6) result = result.substring(0, st) + result.substring(end); // pass to next else pos = end; } else // done break; } return result; } private static boolean cleanPositions(String baseName, List<Position> positions, DimensionId dim) { // remove fixed dim int value = -1; for (Position position : positions) { if (StringUtil.equals(position.baseName, baseName)) { final int v = position.getValue(dim); if (v != -1) { if (value == -1) value = v; else if (value != v) { // variable --> stop value = -1; break; } } } } // fixed dimension ? --> remove it if (value != -1) { for (Position position : positions) { if (StringUtil.equals(position.baseName, baseName)) { if (position.getValue(dim) != -1) position.removeChunk(dim); } } return true; } return false; } private static Position getPosition(String filename) { // get filename without extension final String name = FileUtil.getFileName(filename, false); final String baseName = getBaseName(name); final Position result = new Position(baseName); final int len = name.length(); // int value; int index = 0; while (index < len) { // get starting digit char index final int startInd = StringUtil.getNextDigitCharIndex(name, index); // we find a digit char ? if (startInd >= 0) { // get ending digit char index int endInd = StringUtil.getNextNonDigitCharIndex(name, startInd); if (endInd < 0) endInd = len; // add number only if < 100000 (else it can be a date or id...) if ((endInd - startInd) < 6) { // get prefix final String prefix = getPositionPrefix(name, startInd - 1); // get value final int value = StringUtil.parseInt(name.substring(startInd, endInd), -1); // add the position info result.addChunk(prefix, value); } // adjust index index = endInd; } else index = len; } return result; } private static String getPositionPrefix(String text, int ind) { if ((ind >= 0) && (ind < text.length())) { // we have a letter at this position if (Character.isLetter(text.charAt(ind))) // get complete prefix return text.substring(StringUtil.getPreviousNonLetterCharIndex(text, ind) + 1, ind + 1); } return ""; } }