endrov.ioImageCollections.EvIONamebasedImageset.java Source code

Java tutorial

Introduction

Here is the source code for endrov.ioImageCollections.EvIONamebasedImageset.java

Source

/***
 * Copyright (C) 2010 Johan Henriksson
 * This code is under the Endrov / BSD license. See www.endrov.net
 * for the full text and how to cite.
 */
package endrov.ioImageCollections;

import javax.swing.*;

import org.apache.commons.io.FilenameUtils;

import java.awt.event.*;
import java.awt.*;
import java.io.*;
import java.util.*;
import java.util.List;
import java.util.regex.Pattern;

import loci.formats.FormatException;
import loci.formats.IFormatReader;
import loci.formats.ImageReader;
import loci.formats.in.TiffReader;

import endrov.core.*;
import endrov.core.log.EvLog;
import endrov.data.EvContainer;
import endrov.data.EvData;
import endrov.data.EvIOData;
import endrov.data.EvPath;
import endrov.data.RecentReference;
import endrov.data.gui.DataMenuExtension;
import endrov.data.gui.EvDataGUI;
import endrov.data.gui.EvDataMenu;
import endrov.gui.EvSwingUtil;
import endrov.gui.icon.BasicIcon;
import endrov.gui.window.EvBasicWindow;
import endrov.typeImageset.*;
import endrov.util.EvBrowserUtil;
import endrov.util.io.EvFileUtil;
import endrov.util.math.EvDecimal;

/**
 * Import a list of images by matching the names
 * @author Johan Henriksson
 *
 */
public class EvIONamebasedImageset implements EvIOData {
    /******************************************************************************************************
     *                               Static: Loading                                                      *
     *****************************************************************************************************/

    /** Path to imageset */
    private File basedir;
    private EvData data;

    private String fileConvention = "";
    private String channelList = "";
    private double resX = 1;
    private double resY = 1;
    private double resZ = 1;

    boolean filesAsStacks = false;

    /**
     * Create a new recording. Basedir points to imageset- ie without the channel name
     */
    public EvIONamebasedImageset(EvData data, File basedir) {
        this.data = data;
        this.basedir = basedir;
        setup();
    }

    public String toString() {
        return getMetadataName();
    }

    public File datadir() {
        return basedir;
    }

    /**
     * Go through all files and put in database
     */
    public void buildDatabase(EvData d) {
        NamebasedDatabaseBuilder b = new NamebasedDatabaseBuilder();
        b.run(d);
        EvBasicWindow.updateWindows();
    }

    public void saveMeta() {
    }

    public RecentReference getRecentEntry() {
        return null;
    }

    /**
     * Imageset specific settings
     */
    public void setup() {
        new FileConvention();
    }

    /**
     * Dialog for selecting sequence of files
     */
    public class FileConvention extends JFrame implements ActionListener {
        static final long serialVersionUID = 0;

        private JButton bRebuild = new JButton("Rebuild database");
        private JButton bSyntax = new JButton("Website");

        private JTextField eSequence = new JTextField("foo-%W-%C-%F-%Z.jpg");
        private JTextField eChannels = new JTextField("chan1,chan2");
        private JTextArea eLog = new JTextArea();

        private JTextField eResX = new JTextField("1");
        private JTextField eResY = new JTextField("1");
        private JTextField eSpacingZ = new JTextField("1");

        private JCheckBox cbFilesAsStacks = new JCheckBox("Interpret files as stacks");

        public FileConvention() {
            setTitle(EndrovCore.programName + " Name based Import File Conventions");

            JPanel input = new JPanel(new GridLayout(8, 1));
            input.add(new JLabel(basedir.toString()));
            input.add(EvSwingUtil.withLabel("Name:", eSequence));
            input.add(new JLabel("Name is case-sensitive!"));
            input.add(EvSwingUtil.withLabel("Channels:", eChannels));

            input.add(EvSwingUtil.withLabel("Resolution X [px/um]:", eResX));
            input.add(EvSwingUtil.withLabel("Resolution Y [px/um]:", eResY));
            input.add(EvSwingUtil.withLabel("Spacing Z [um/plane]:", eSpacingZ));

            input.add(cbFilesAsStacks);

            eSequence.setPreferredSize(new Dimension(430, 20));
            eChannels.setPreferredSize(new Dimension(400, 20));

            eSequence.setText(fileConvention);
            eChannels.setText(channelList);

            JPanel bp = new JPanel(new GridLayout(1, 2));
            bp.add(bRebuild);
            bp.add(bSyntax);

            JPanel left = new JPanel(new BorderLayout());
            left.add(input, BorderLayout.NORTH);
            left.add(bp, BorderLayout.SOUTH);

            setLayout(new GridLayout(1, 2));
            add(left);
            add(new JScrollPane(eLog, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
                    JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS));
            eLog.setEditable(false);

            bRebuild.addActionListener(this);
            bSyntax.addActionListener(this);

            pack();
            setBounds(0, 100, 1000, 400);
            setVisible(true);
            setDefaultCloseOperation(DISPOSE_ON_CLOSE);
        }

        public void actionPerformed(ActionEvent e) {
            if (e.getSource() == bSyntax)
                EvBrowserUtil.displayURL(EndrovCore.websiteWikiPrefix + "Importing_collections_of_images");
            else if (e.getSource() == bRebuild) {
                fileConvention = eSequence.getText();
                channelList = eChannels.getText();
                resX = Double.parseDouble(eResX.getText());
                resY = Double.parseDouble(eResY.getText());
                resZ = Double.parseDouble(eSpacingZ.getText());
                filesAsStacks = cbFilesAsStacks.isSelected();
                NamebasedDatabaseBuilder b = new NamebasedDatabaseBuilder();
                b.run(data);
                eLog.setText(b.rebuildLog.toString());
                EvBasicWindow.updateWindows();
            }
        }
    }

    //////////////////////////////////////////////////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////////////////////////////////////////////////

    private static class FileInfo {
        File f;
        int channelNum = 0;
        int slice = 0;
        int frame = 0;
        String well = null;
        String dataset;
    }

    /**
     * Class for building database. Has to be a class because java has immutable primitives
     */
    private class NamebasedDatabaseBuilder {
        //File[] fileList;
        List<File> fileList = new ArrayList<File>();
        int currentFile = 0;
        private StringBuffer rebuildLog = new StringBuffer();

        private int countFilesAdded = 0;

        private Integer minZ = null;

        private void getAllFiles(List<File> files, File dir) {
            for (File f : dir.listFiles())
                if (!f.getName().startsWith(".")) {
                    if (f.isDirectory()) {
                        getAllFiles(files, f);
                    } else {
                        files.add(f);
                    }
                }
        }

        public void run(EvData data) {
            minZ = null;
            Vector<String> channelVector = new Vector<String>();
            try {
                File dir = basedir;
                fileList.clear();
                getAllFiles(fileList, dir);

                //Parse list of channels into vector
                StringTokenizer ctok = new StringTokenizer(channelList, ",");
                while (ctok.hasMoreTokens())
                    channelVector.add(ctok.nextToken());

                /*
                Imageset im;
                Collection<Imageset> ims=data.getObjects(Imageset.class);
                if(!ims.isEmpty())
                   im=ims.iterator().next();
                else
                   {
                   im=new Imageset();
                   data.metaObject.put("im", im);
                   }
                    
                //Remove all channels
                for(String s:im.getChannels().keySet())
                   im.metaObject.remove(s);
                */

                //Get the imageset and clean it up
                for (EvPath p : data.getIdObjectsRecursive(Imageset.class).keySet())
                    p.getParent().getObject().removeMetaObjectByValue(p.getObject());

                //Go through list of files, just to see what there is
                List<FileInfo> files = new LinkedList<FileInfo>();
                File f;
                currentFile = 0;
                minZ = null;
                while ((f = nextFile()) != null) {
                    FileInfo info = parse(f);
                    if (info != null)
                        files.add(info);
                }

                //Add all the files
                for (FileInfo info : files)
                    buildAddFile(data, info, channelVector);
            } catch (Exception e) {
                JOptionPane.showMessageDialog(null, "Error rebuilding: " + e.getMessage());
                e.printStackTrace();
            }

            rebuildLog.append("Total images identified: " + countFilesAdded);
        }

        /**
         * Parse out information from filename. Return info if parse successful
         */
        private FileInfo parse(File f) throws Exception {
            FileInfo info = new FileInfo();
            info.f = f;
            //String filename=f.getName();

            System.out.println(File.pathSeparatorChar);
            String filename = getRelativePath(f.getPath(), basedir.getPath(), File.separator);

            int i = 0;
            int j = 0;
            while (i < fileConvention.length()) {
                if (j == filename.length())
                    break;
                if (fileConvention.charAt(i) == '%') {
                    char type = fileConvention.charAt(i + 1);
                    i += 2;
                    if (type == '%')
                        j++;
                    else if (type == 'D') {
                        StringBuilder sb = new StringBuilder();

                        while (j < filename.length()) {
                            char c = filename.charAt(j);
                            if (c != '.')//Character.isLetter(c) || Character.isDigit(c))
                            {
                                if (c == '/')
                                    c = '@';
                                sb.append(c);
                                j++;
                            } else
                                break;
                        }
                        info.dataset = sb.toString();
                        if (info.dataset.length() == 0) {
                            rebuildLog.append("Not matching " + filename + " Missing parameter " + type
                                    + ", filename pos" + j + "\n");
                            return null;
                        }
                    } else if (type == 'W') {
                        StringBuilder sb = new StringBuilder();

                        while (j < filename.length()) {
                            char c = filename.charAt(j);
                            if (Character.isLetter(c) || Character.isDigit(c)) {
                                sb.append(c);
                                j++;
                            } else
                                break;
                        }
                        info.well = sb.toString();
                        if (info.well.length() == 0) {
                            rebuildLog.append("Not matching " + filename + " Missing parameter " + type
                                    + ", filename pos" + j + "\n");
                            return null;
                        }
                    } else {
                        String params = parseInt(filename.substring(j));
                        if (params.equals("")) {
                            rebuildLog.append("Not matching " + filename + " Missing parameter " + type
                                    + ", filename pos" + j + "\n");
                            return null;
                        } else {
                            j += params.length();
                            int parami = Integer.parseInt(params);
                            if (type == 'C')
                                info.channelNum = parami;
                            else if (type == 'F')
                                info.frame = parami;
                            else if (type == 'Z')
                                info.slice = parami;
                            else if (type == '#')
                                ;
                            else {
                                rebuildLog.append("Unknown parameter: " + type + "\n");
                                return null;
                            }
                        }
                    }
                } else if (fileConvention.charAt(i) == filename.charAt(j)) {
                    i++;
                    j++;
                } else {
                    rebuildLog.append("Not matching: " + filename + " rulepos " + i + " namepos " + j + "\n");
                    return null;
                }
            }

            //If everything was matched, continue
            if (j == filename.length()) {
                //Keep track of smallest Z seen
                if (minZ == null || info.slice < minZ)
                    minZ = info.slice;

                return info;
            } else {
                rebuildLog.append("Not matching: " + filename + " Premature end of filename\n");
                return null;
            }
        }

        /**
         * Add file to channels
         */
        private void buildAddFile(EvContainer con, FileInfo info, List<String> channelVector) throws Exception {
            if (info.channelNum >= channelVector.size())
                throw new Exception("For " + info.f + ", no channel for index " + info.channelNum
                        + ". Note that channels start counting from 0.\n"
                        + "If your channels start from 1 then give the first channel 0 an arbitrary name, it will not be used.");

            String channelName = channelVector.get(info.channelNum);

            //Get the right well / imageset
            String nameImageset = info.well;

            if (info.dataset != null)
                nameImageset = info.dataset; //TODO handle dataset and well simulatenously

            if (nameImageset == null)
                nameImageset = "im";
            Imageset im = (Imageset) con.getChild(nameImageset);
            if (im == null)
                con.putChild(nameImageset, im = new Imageset());

            //Get channel
            EvChannel ch = im.getCreateChannel(channelName);
            System.out.println(ch);

            //Get stack
            EvStack stack = ch.getStack(new EvDecimal(info.frame));
            if (stack == null) {
                stack = new EvStack();
                ch.putStack(new EvDecimal(info.frame), stack);
            }

            int numPlanesInFile = 1;

            if (filesAsStacks) {
                numPlanesInFile = countImagePlanes(info.f);
                System.out.println("********* " + numPlanesInFile);
            }

            //Create planes
            for (int i = 0; i < numPlanesInFile; i++) {
                EvImagePlane evim = new EvImagePlane();
                stack.setRes(resX, resY, resZ);
                evim.io = new EvSingleImageFileReader(info.f, i);

                int slice = i + info.slice - minZ;
                stack.putPlane(slice, evim);

                String filename = info.f.getName();
                String newLogEntry = filename + " Ch: " + channelName + " Fr: " + info.frame + " Sl: " + slice
                        + "\n";
                System.out.println(newLogEntry);
                rebuildLog.append(newLogEntry);
                countFilesAdded++;
            }

        }

        /** Get the next int */
        private String parseInt(String s) {
            String part = "";
            int stringpos = 0;
            while ("1234567890".indexOf(s.charAt(stringpos)) >= 0) {
                part = part + s.charAt(stringpos);
                stringpos++;
            }
            return part;
        }

        /** Get the next file to assign frame/slice */
        private File nextFile() {
            if (currentFile < fileList.size()) {
                File f = fileList.get(currentFile);
                currentFile++;
                return f;
            } else
                return null;
        }

    }

    public String getMetadataName() {
        return basedir.getName();
    }

    public void saveData(EvData d, EvData.FileIOStatusCallback cb) {
        JOptionPane.showMessageDialog(null,
                "This image format does not support saving. Convert to e.g. OST instead");
    }

    public void close() throws IOException {
    }

    /******************************************************************************************************
     * Plugin declaration
     *****************************************************************************************************/
    public static void initPlugin() {
    }

    static {
        EvDataMenu.addExtensions(new DataMenuExtension() {
            public void buildData(JMenu menu) {

            }

            public void buildOpen(JMenu menu) {
                final JMenuItem miLoadNamebasedImageset = new JMenuItem("Load namebased imageset");
                miLoadNamebasedImageset.setIcon(BasicIcon.iconMenuLoad);
                EvBasicWindow.addMenuItemSorted(menu, miLoadNamebasedImageset, "data_open_namebased");

                ActionListener listener = new ActionListener() {
                    public void actionPerformed(ActionEvent e) {
                        File filename = EvBasicWindow.openDialogChooseDir();
                        if (filename != null) {
                            EvData data = new EvData();
                            data.io = new EvIONamebasedImageset(data, filename);
                            EvDataGUI.registerOpenedData(data);
                        }
                    }
                };

                miLoadNamebasedImageset.addActionListener(listener);
            }

            public void buildSave(JMenu menu, final EvData meta) {
                if (meta.io instanceof EvIONamebasedImageset) {
                    JMenuItem miSetup = new JMenuItem("Setup");
                    menu.add(miSetup);
                    miSetup.addActionListener(new ActionListener() {
                        public void actionPerformed(ActionEvent e) {
                            ((EvIONamebasedImageset) meta.io).setup();
                        }
                    });
                }
            }
        });

    }

    /**
     * Get the relative path from one file to another, specifying the directory separator. 
     * If one of the provided resources does not exist, it is assumed to be a file unless it ends with '/' or
     * '\'.
     * 
     * @param targetPath targetPath is calculated to this file
     * @param basePath basePath is calculated from this file
     * @param pathSeparator directory separator. The platform default is not assumed so that we can test Unix behaviour when running on Windows (for example)
     * @return
     */
    public static String getRelativePath(String targetPath, String basePath, String pathSeparator) {
        // Normalize the paths
        String normalizedTargetPath = FilenameUtils.normalizeNoEndSeparator(targetPath);
        String normalizedBasePath = FilenameUtils.normalizeNoEndSeparator(basePath);

        // Undo the changes to the separators made by normalization
        if (pathSeparator.equals("/")) {
            normalizedTargetPath = FilenameUtils.separatorsToUnix(normalizedTargetPath);
            normalizedBasePath = FilenameUtils.separatorsToUnix(normalizedBasePath);

        } else if (pathSeparator.equals("\\")) {
            normalizedTargetPath = FilenameUtils.separatorsToWindows(normalizedTargetPath);
            normalizedBasePath = FilenameUtils.separatorsToWindows(normalizedBasePath);
        } else {
            throw new IllegalArgumentException("Unrecognised dir separator '" + pathSeparator + "'");
        }

        String[] base = normalizedBasePath.split(Pattern.quote(pathSeparator));
        String[] target = normalizedTargetPath.split(Pattern.quote(pathSeparator));

        // First get all the common elements. Store them as a string,
        // and also count how many of them there are.
        StringBuffer common = new StringBuffer();

        int commonIndex = 0;
        while (commonIndex < target.length && commonIndex < base.length
                && target[commonIndex].equals(base[commonIndex])) {
            common.append(target[commonIndex] + pathSeparator);
            commonIndex++;
        }

        if (commonIndex == 0) {
            // No single common path element. This most
            // likely indicates differing drive letters, like C: and D:.
            // These paths cannot be relativized.
            throw new PathResolutionException("No common path element found for '" + normalizedTargetPath
                    + "' and '" + normalizedBasePath + "'");
        }

        // The number of directories we have to backtrack depends on whether the
        // base is a file or a dir
        // For example, the relative path from
        //
        // /foo/bar/baz/gg/ff to /foo/bar/baz
        //
        // ".." if ff is a file
        // "../.." if ff is a directory
        //
        // The following is a heuristic to figure out if the base refers to a file
        // or dir. It's not perfect, because
        // the resource referred to by this path may not actually exist, but it's
        // the best I can do
        boolean baseIsFile = true;

        File baseResource = new File(normalizedBasePath);

        if (baseResource.exists()) {
            baseIsFile = baseResource.isFile();

        } else if (basePath.endsWith(pathSeparator)) {
            baseIsFile = false;
        }

        StringBuffer relative = new StringBuffer();

        if (base.length != commonIndex) {
            int numDirsUp = baseIsFile ? base.length - commonIndex - 1 : base.length - commonIndex;

            for (int i = 0; i < numDirsUp; i++)
                relative.append(".." + pathSeparator);
        }
        relative.append(normalizedTargetPath.substring(common.length()));
        return relative.toString();
    }

    static class PathResolutionException extends RuntimeException {
        private static final long serialVersionUID = 1L;

        PathResolutionException(String msg) {
            super(msg);
        }
    }

    /**
     * Count number of planes in file
     * 
     * @throws IOException 
     */
    public static int countImagePlanes(File file) throws IOException {
        try {
            String fend = EvFileUtil.fileEnding(file);
            fend = fend.toLowerCase();
            //Use JAI if possible, it can be assumed to be very fast

            //Rely on Bioformats in the worst case. Use the most stupid reader available, or bio-formats might attempt
            //detecting a special format
            IFormatReader reader;
            if (fend != null && (fend.equals("tiff") || fend.equals("tif"))) {
                TiffReader tr = new TiffReader();
                tr.setGroupFiles(false);
                reader = tr;
            } else {
                reader = new ImageReader();
            }
            //reader.setAllowOpenFiles(false);  //for scifio

            reader.setId(file.getAbsolutePath());

            int count = reader.getImageCount();

            //         String[] dimTypes=reader.getChannelDimTypes();
            //      int[] dimLengths=reader.getChannelDimLengths();

            reader.close();

            return count;
        } catch (FormatException e) {
            EvLog.printError("Bioformats failed to read image " + file, null);
            throw new IOException(e.getMessage());
        }
    }

}