pipeline.misc_util.Utils.java Source code

Java tutorial

Introduction

Here is the source code for pipeline.misc_util.Utils.java

Source

/*******************************************************************************
 * Parismi v0.1
 * Copyright (c) 2009-2015 Cinquin Lab.
 * All rights reserved. This code is made available under a dual license:
 * the two-clause BSD license or the GNU Public License v2.
 ******************************************************************************/
package pipeline.misc_util;

import ij.IJ;
import ij.ImagePlus;
import ij.ImageStack;
import ij.io.FileInfo;
import ij.io.FileOpener;
import ij.io.TiffDecoder;
import ij.plugin.FileInfoVirtualStack;
import ij.text.TextPanel;

import java.awt.Color;
import java.awt.Frame;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StreamTokenizer;
import java.io.StringReader;
import java.io.StringWriter;
import java.lang.reflect.Field;
import java.nio.channels.ClosedByInterruptException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;

import ncsa.util.ReaderTokenizer;

import org.apache.commons.lang3.text.WordUtils;
import org.jdom.Content;
import org.jdom.Document;
import org.jdom.input.SAXBuilder;
import org.jfree.data.xy.XYSeries;
import org.xml.sax.InputSource;

import pipeline.A0PipeLine_Manager;
import pipeline.data.IPluginIOHyperstack;
import pipeline.data.IPluginIOStack;
import pipeline.data.PluginIOCells;
import pipeline.data.PluginIOImage.PixelType;

import com.sun.jna.Callback;
import com.sun.jna.Callback.UncaughtExceptionHandler;
import com.thoughtworks.xstream.XStream;

/**
 * Various utilities, used mainly by the pipeline and parameters.
 *
 */
public class Utils {

    public static final String fileNameSeparator = "/";

    public static final String DEFAULT_CHANNEL_NAME_WHEN_ONLY_1_CHANNEL = "Ch 0"; // changing this will break some
    // things

    public static final Color COLOR_FOR_EVEN_ROWS = new Color(255, 255, 200);
    public static final Color COLOR_FOR_SELECTED_ROWS = new Color(200, 200, 255);
    public static final Color COLOR_FOR_ODD_ROWS = new Color(230, 230, 255);

    public static final int MAXIMAL_BUTTON_HEIGHT = 15;

    public static int logLevelThreshold = 7;

    public static final String[] LogLevelNames = new String[] { "CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG" };

    public static final class LogLevel {
        public static final int CRITICAL = 0, ERROR = 1, WARNING = 2, INFO = 3, DEBUG = 4;
    }

    private static FileWriter writer;

    public static boolean skipListsInTableTextExport = true;

    private static boolean failedToOpenLogFile = false;// will be set to true if e.g. log file is not writable

    private static String logFileName = null;

    private static boolean prependTime = false;

    public static void setPrependTime(boolean on) {
        Utils.prependTime = on;
    }

    private static final ThreadLocal<SimpleDateFormat> dateFormatter = new ThreadLocal<SimpleDateFormat>() {
        @Override
        protected SimpleDateFormat initialValue() {
            return new SimpleDateFormat("E dd MMM yy HH:mm:ss");
        }
    };

    public static void setLogFile(String logFile) {
        if (writer != null) {
            try {
                writer.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            writer = null;
            failedToOpenLogFile = false;
        }
        logFileName = logFile;
        if (logFile == null) {
            failedToOpenLogFile = true;
        }
    }

    private static String logPrefix = null;

    public static void setLogPrefix(String prefix) {
        Utils.logPrefix = prefix;
    }

    private static boolean copyLogMessagesToStdOut = true;

    public static void setCopyLogMessagesToStdOut(boolean on) {
        Utils.copyLogMessagesToStdOut = on;
    }

    private static void log(final String message, int logLevel, boolean suppressTime) {
        if (suppressLog || (logLevel > logLevelThreshold))
            return;

        final String message2 = logPrefix != null ? logPrefix + message : message;

        final String finalMessage = prependTime && !suppressTime
                ? dateFormatter.get().format((new Date())) + " " + message2
                : message2;

        try {
            if ((!failedToOpenLogFile) && (writer == null)) {
                if (logFileName == null) {
                    System.err.println("Warning: log file name not set; using default");
                    logFileName = A0PipeLine_Manager.getBaseDir() + "/pipeline_log.txt";
                }
                try {
                    writer = new FileWriter(new File(logFileName), true);
                } catch (Exception e) {
                    failedToOpenLogFile = true;
                    if (headless) {
                        System.err.println("Exception while trying to open log file: " + e);
                    } else {
                        IJ.log("Exception while trying to open log file: " + e);
                    }
                }
            }
            if (writer != null) {
                writer.write(finalMessage + "\n");
                writer.flush();
            } else {
                copyLogMessagesToStdOut = true;
            }
        } catch (IOException e) {
            if (headless) {
                System.err.println("Exception while logging " + finalMessage);
            } else {
                IJ.log("Exception while logging " + finalMessage);
            }
            Utils.printStack(e);
        }

        if (copyLogMessagesToStdOut) {
            if (headless) {
                System.err.println(finalMessage);
            } else
                SwingUtilities.invokeLater(() -> {
                    IJ.log(finalMessage);
                    if (logLevel <= LogLevel.WARNING) {
                        try {
                            Field logPanelField = IJ.class.getDeclaredField("logPanel");
                            logPanelField.setAccessible(true);
                            TextPanel panel = (TextPanel) logPanelField.get(null);
                            ((Frame) panel.getParent()).toFront();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                });
        }

    }

    /**
     * Log the passed string to the ImageJ log window.
     * 
     * @param logLevel
     *            Only log if logLevel smaller than current threshold.
     *            Log levels:
     *            0: critical
     *            1: error
     *            2: warning
     *            3: info
     *            4: debug
     *            5: verbose debug
     *            6: verbose verbose debug
     *            7: .....
     */
    public final static void log(final String message, int logLevel) {
        log(message, logLevel, false);
    }

    public static class ImageOpenFailed extends Exception {

        private static final long serialVersionUID = 1L;

        public ImageOpenFailed(String s) {
            super(s);
        }

        public ImageOpenFailed(String s, Throwable cause) {
            super(s, cause);
        }
    }

    public static void openVirtualTiff(File file, ImagePlus image, boolean displayErrorMessage)
            throws ImageOpenFailed {
        TiffDecoder td = new TiffDecoder(file.getParent(), file.getName());

        @SuppressWarnings("null")
        String shortName = FileNameUtils.compactPath(file.getAbsolutePath());

        FileInfo[] fiArray = null;
        try {
            fiArray = td.getTiffInfo();
        } catch (IOException e) {
            if (displayErrorMessage)
                Utils.displayMessage("Error opening tiff " + shortName, false, LogLevel.ERROR);
            throw new ImageOpenFailed("Error opening virtual TIFF file " + shortName, e);
        }

        if (fiArray == null || fiArray.length == 0) {
            if (displayErrorMessage)
                Utils.displayMessage(
                        "Error opening " + shortName + ": this does not appear to be a valid TIFF stack", true,
                        LogLevel.ERROR);
            throw new ImageOpenFailed("Error opening virtual TIFF file " + shortName);
        }

        FileInfoVirtualStack fivs = new FileInfoVirtualStack();
        FileOpener.setSilentMode(true);
        fivs.info = fiArray;
        fivs.openButDontShow(image);
    }

    public static String chopOffString(String s, int maxLength) {
        if (s == null)
            return "";
        if (s.length() <= maxLength) {
            return s;
        } else {
            return s.substring(0, min(maxLength, s.length())) + "...";
        }
    }

    public static String chopOffStringBeginning(String s, int maxLength) {
        if (s == null)
            return "";
        if (s.length() <= maxLength) {
            return s;
        } else {
            return "..." + s.substring(max(s.length() - maxLength, 0));
        }
    }

    public static int min(int a, int b) {
        if (a < b)
            return a;
        else
            return b;
    }

    public static int max(int a, int b) {
        if (a > b)
            return a;
        else
            return b;
    }

    // From http://forums.thedailywtf.com/forums/p/2806/72054.aspx#72054
    public static String encodeHTML(String s) {
        StringBuffer out = new StringBuffer("<html>");
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if (c > 127 || c == '"' || c == '<' || c == '>') {
                out.append("&#" + (int) c + ";");
            } else {
                out.append(c);
            }
        }
        out.append("</html>");
        return out.toString();
    }

    public static void displayMessage(final String s, boolean logMessage, final int logLevel) {
        Utils.log("Displaying message: " + s, LogLevel.ERROR);
        if ((!headless) && (!suppressWarningPopups))
            SwingUtilities.invokeLater(() -> {
                JLabel textLabel = new JLabel(encodeHTML(WordUtils.wrap(s, 50)).replace("\n", "<br>\n"));
                int level;
                if (logLevel >= LogLevel.INFO)
                    level = JOptionPane.INFORMATION_MESSAGE;
                else if (logLevel == LogLevel.WARNING)
                    level = JOptionPane.WARNING_MESSAGE;
                else
                    level = JOptionPane.ERROR_MESSAGE;
                JOptionPane optionPane = new JOptionPane();
                optionPane.setMessageType(level);
                optionPane.setMessage(textLabel);
                JDialog dialog = optionPane.createDialog("Pipeline message");
                dialog.setAlwaysOnTop(true);
                dialog.setVisible(true);
                // JOptionPane.showMessageDialog(null, textLabel,"Pipeline message",level);
            });
    }

    /**
     * True if pipeline is not attached to a GUI.
     */
    public static boolean headless;

    public static boolean suppressLog;

    private static boolean VNCScrollWheel = false;

    public static UncaughtExceptionHandler callbackExceptionHandler = new UncaughtExceptionHandler() {

        @Override
        public void uncaughtException(Callback arg0, Throwable arg1) {
            Utils.log("Uncaught exception in JNA callback " + arg0.toString(), LogLevel.ERROR);
            Utils.printStack(arg1);
        }
    };

    public static boolean suppressWarningPopups = false;

    public static float getMouseWheelClickFactor() {
        if (VNCScrollWheel)
            return 0.1f;
        else
            return 1f;
    }

    public static void add(float[] a, float[] b) {
        for (int i = 0; i < a.length; i++) {
            a[i] = a[i] + b[i];
        }
    }

    public static void subtract(float[] a, float[] b) {
        for (int i = 0; i < a.length; i++) {
            a[i] = a[i] - b[i];
        }
    }

    public static int indexOfMin(float[] a) {
        float min = Float.MAX_VALUE;
        int min_index = -1;
        for (int i = 0; i < a.length; i++) {
            if (a[i] < min) {
                min = a[i];
                min_index = i;
            }
        }
        return min_index;
    }

    public static int indexOfMax(float[] a) {
        float max = -Float.MAX_VALUE;
        int max_index = -1;
        for (int i = 0; i < a.length; i++) {
            if (a[i] > max) {
                max = a[i];
                max_index = i;
            }
        }
        return max_index;
    }

    public static float[] arrayAbs(float[] a) {
        float[] b = new float[a.length];
        for (int i = 0; i < a.length; i++) {
            b[i] = Math.abs(a[i]);
        }
        return b;
    }

    public static float findMax(float[] a) {
        float max = 0;
        for (float element : a) {
            if (element > max)
                max = element;
        }
        return max;
    }

    public static float findMax(short[] a) {
        float max = 0;
        for (short element : a) {
            if (((element & 0xffff)) > max)
                max = (element & 0xffff);
        }
        return max;
    }

    public static float findMax(byte[] a) {
        float max = 0;
        for (byte element : a) {
            if (((element & 0xff)) > max)
                max = (element & 0xff);
        }
        return max;
    }

    private static void findMinMax(byte[] a, MinMax m) {
        float max = -Float.MAX_VALUE;
        float min = Float.MAX_VALUE;
        for (byte element : a) {
            if (((element & 0xff)) > max)
                max = (element & 0xff);
            if (((element & 0xff)) < min)
                min = (element & 0xff);
        }
        m.max = max;
        m.min = min;
    }

    private static void findMinMax(short[] a, MinMax m) {
        float max = -Float.MAX_VALUE;
        float min = Float.MAX_VALUE;
        for (short element : a) {
            if (((element & 0xff)) > max)
                max = (element & 0xffff);
            if (((element & 0xff)) < min)
                min = (element & 0xffff);
        }
        m.max = max;
        m.min = min;
    }

    private static void findMinMax(float[] a, MinMax m) {
        float max = -Float.MAX_VALUE;
        float min = Float.MAX_VALUE;
        for (float element : a) {
            if (element > max)
                max = element;
            if (element < min)
                min = element;
        }
        m.max = max;
        m.min = min;
    }

    public static void updateRangeInStack(ImagePlus imp) {
        float max = -Float.MAX_VALUE;
        float min = Float.MAX_VALUE;
        int nSlices = imp.getNSlices();
        int nChannels = imp.getNChannels();
        MinMax m = new MinMax();
        for (int i = 1; i <= nSlices; i++) {
            Object pixels = imp.getStack().getPixels((i - 1) * nChannels + imp.getChannel());
            float sliceMax = 0;
            float sliceMin = 0;
            if (pixels instanceof float[]) {
                findMinMax((float[]) pixels, m);
            } else if (pixels instanceof byte[]) {
                findMinMax((byte[]) pixels, m);
            } else if (pixels instanceof short[]) {
                findMinMax((short[]) pixels, m);
            }
            sliceMax = m.max;
            sliceMin = m.min;
            if (sliceMax > max)
                max = sliceMax;
            if (sliceMin < min)
                min = sliceMin;
        }

        imp.setDisplayRange(min, max);
        imp.updateChannelAndDraw();
    }

    public static void updateRangeInRegularImp(ImagePlus imp) {
        Object pixels = imp.getProcessor().getPixels();
        MinMax m = new MinMax();
        if (pixels instanceof float[])
            findMinMax((float[]) pixels, m);
        else if (pixels instanceof byte[])
            findMinMax((byte[]) pixels, m);
        else if (pixels instanceof short[])
            findMinMax((short[]) pixels, m);
        imp.setDisplayRange(m.min, m.max);
        imp.updateChannelAndDraw();
    }

    public static String[] concatenateArrays(String[] array1, String[] array2) {
        if (array1 == null)
            return array2.clone();
        if (array2 == null)
            return array1.clone();
        String[] newArray = new String[array1.length + array2.length];
        System.arraycopy(array1, 0, newArray, 0, array1.length);
        System.arraycopy(array2, 0, newArray, array1.length, array2.length);
        return newArray;
    }

    public static int[] arraySubSlice(int[] array, int start, int stop) {
        int[] newArray = new int[stop - start + 1];
        for (int i = start; i <= stop; i++) {
            newArray[i - start] = array[i - start];
        }
        return newArray;
    }

    public static int[] indexesOf(String[] masterList, String[] elementsToSelect) {
        int[] result = new int[elementsToSelect.length];

        int resultIndex = 0;
        for (String element : elementsToSelect) {
            result[resultIndex] = Utils.indexOf(masterList, element);
            if (result[resultIndex] == -1) {
                Utils.log("Channel " + element + " not present in " + printStringArray(masterList), LogLevel.DEBUG);
            } else
                resultIndex++;
        }

        if (resultIndex < elementsToSelect.length) {
            // we didn't find some channels, so need to resize the array
            int[] newResult = new int[resultIndex];
            for (int i = 0; i < resultIndex; i++) {
                newResult[i] = result[i];
            }
            result = newResult;
        }

        return result;
    }

    private static float max(Object o) {
        if (o instanceof float[]) {
            float the_max = Float.MIN_VALUE;
            float[] fa = (float[]) o;
            for (float element : fa) {
                if (element > the_max)
                    the_max = element;
            }
            return the_max;
        } else if (o instanceof short[]) {
            int the_max = Integer.MIN_VALUE;
            short[] fa = (short[]) o;
            for (short element : fa) {
                if ((element & 0xffff) > the_max)
                    the_max = (element & 0xffff);
            }
            return the_max;
        } else if (o instanceof byte[]) {
            int the_max = Integer.MIN_VALUE;
            byte[] fa = (byte[]) o;
            for (byte element : fa) {
                if ((element & 0xff) > the_max)
                    the_max = (element & 0xff);
            }
            return the_max;
        } else
            throw new RuntimeException("Pixel type not handled in max");
    }

    private static float min(Object o) {

        if (o instanceof float[]) {
            float the_min = Float.MAX_VALUE;
            float[] fa = (float[]) o;
            for (float element : fa) {
                if (element < the_min)
                    the_min = element;
            }
            return the_min;
        } else if (o instanceof short[]) {
            int the_min = Integer.MAX_VALUE;
            short[] fa = (short[]) o;
            for (short element : fa) {
                if ((element & 0xffff) < the_min)
                    the_min = (element & 0xffff);
            }
            return the_min;
        } else if (o instanceof byte[]) {
            int the_min = Integer.MAX_VALUE;
            byte[] fa = (byte[]) o;
            for (byte element : fa) {
                if ((element & 0xff) < the_min)
                    the_min = (element & 0xff);
            }
            return the_min;
        } else
            throw new RuntimeException("Pixel type not handled in min");
    }

    public static float getMin(IPluginIOStack source) {
        if (source == null) {
            Utils.log("Source stack in null in getStackMax", LogLevel.WARNING);
            return 0;
        }
        float the_min = Float.MAX_VALUE;
        for (int i = 0; i < source.getDimensions().depth; i++) {
            float slice_min = (min(source.getPixels(i)));
            if (slice_min < the_min)
                the_min = slice_min;
        }
        return the_min;
    }

    public static float getMax(IPluginIOStack source) {
        if (source == null) {
            Utils.log("Source stack in null in getStackMax", LogLevel.WARNING);
            return 0;
        }
        float max = Float.MIN_VALUE;
        for (int i = 0; i < source.getDimensions().depth; i++) {
            float sliceMax = (max(source.getPixels(i)));
            if (sliceMax > max)
                max = sliceMax;
        }
        return max;
    }

    public static float getStackMax(ImagePlus source) {
        if (source == null) {
            Utils.log("Source stack in null in getStackMax", LogLevel.INFO);
            return 0;
        }
        ImageStack stack = source.getStack();
        float max = Float.MIN_VALUE;
        for (int i = 1; i <= stack.getSize(); i++) {
            float sliceMax = (max(stack.getProcessor(i).getPixels()));
            if (sliceMax > max)
                max = sliceMax;
        }
        return max;
    }

    public static float getStackMin(ImagePlus source) {
        if (source == null) {
            Utils.log("Source stack in null in getStackMin", LogLevel.INFO);
            return 0;
        }
        ImageStack stack = source.getStack();
        float min = Float.MAX_VALUE;
        for (int i = 1; i <= stack.getSize(); i++) {
            float sliceMin = (float) stack.getProcessor(i).getMin();
            if (sliceMin < min)
                min = sliceMin;
        }
        return min;
    }

    public static String printIntArray(int[] a) {
        StringBuilder r = new StringBuilder();
        for (int element : a) {
            r.append(element).append(", ");
        }
        return r.toString();
    }

    public static String printStringArray(String[] a) {
        StringBuilder r = new StringBuilder();
        for (String element : a) {
            r.append(element).append(", ");
        }
        return r.toString();
    }

    public static String printObjectArray(Object[] a) {
        StringBuilder r = new StringBuilder();
        for (Object element : a) {
            r.append(element).append(", ");
        }
        return r.toString();
    }

    public static String printStringArray(String[] a, String delimiter) {
        StringBuilder r = new StringBuilder();
        for (String element : a) {
            r.append(element).append(delimiter);
        }
        return r.toString();
    }

    public static String printStringArray(List<String> a) {
        StringBuilder r = new StringBuilder();
        for (String anA : a) {
            r.append(anA).append(", ");
        }
        return r.toString();
    }

    public static int indexOf(String[] a, String b) {
        if (a == null || b == null)
            return -1;
        for (int i = 0; i < a.length; i++) {
            if (b.equals(a[i]))
                return i;
        }
        return -1;
    }

    public static int indexOf(int[] a, int b) {
        if (a == null)
            return -1;
        for (int i = 0; i < a.length; i++) {
            if (b == a[i])
                return i;
        }
        return -1;
    }

    static public boolean isParsableToInt(String i) {
        try {
            Integer.parseInt(i);
            return true;
        } catch (NumberFormatException nfe) {
            return false;
        }
    }

    static public boolean isParsableToFloat(String i) {
        try {
            Float.parseFloat(i);
            return true;
        } catch (NumberFormatException nfe) {
            return false;
        }
    }

    static public int parseAndThrowRuntimeException(String i) {
        try {
            return Integer.parseInt(i);
        } catch (NumberFormatException nfe) {
            throw new RuntimeException("String " + i + " not parsable to int");
        }

    }

    static public void printStack(Throwable e, int logLevel) {
        if (suppressLog)
            return;
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        e.printStackTrace(pw);

        Utils.log("\n" + "\n" + sw.toString(), logLevel, true);// "\n"+"\n"+e.getMessage()+"\n\n"+e.toString()+
    }

    public static boolean causedByInterruption(Throwable e) {
        if (e instanceof PluginRuntimeException && ((PluginRuntimeException) e).unmaskable)
            return false;
        while (e != null) {
            if (e instanceof InterruptedException || e instanceof ClosedByInterruptException)
                return true;
            e = e.getCause();
        }
        return false;
    }

    static public void printStack(Throwable e) {
        printStack(e, LogLevel.CRITICAL);
    }

    static public int nSlices(ImagePlus imp) {
        boolean isHyperStack = imp.isHyperStack();

        int nSlices, nChannels;
        if (isHyperStack) {
            nChannels = imp.getNChannels();
            nSlices = imp.getStackSize() / nChannels;
        } else {
            nChannels = 1;
            nSlices = imp.getStackSize();
        }
        return nSlices;
    }

    static public String objectToXMLString(Object o) {
        XStream workonStream = new XStream(A0PipeLine_Manager.reflectionProvider);

        String w = null;
        if (o instanceof XYSeries)
            try {
                w = workonStream.toXML(((XYSeries) o).clone());// To remove listener that messes up deserialization
            } catch (CloneNotSupportedException e) {
                Utils.printStack(e);
            }
        else
            w = workonStream.toXML(o);

        return w;
    }

    static public Content objectToXML(Object o) {
        XStream workonStream = new XStream();
        String w = workonStream.toXML(o);
        // parse the XML string for insertion into our tree; that's a bit wasteful
        // is it possible to get an element instead of a string out of XStream?

        Document doc = null;
        try {
            SAXBuilder builder = new SAXBuilder();
            doc = builder.build(new InputSource(new StringReader(w)));
        } catch (Exception e) {
            // Utils.printStack(e);
            log("Failed to parse WORK_ON_CHANNEL_FIELD" + w, LogLevel.ERROR);
        }

        return (doc == null) ? null : doc.getRootElement().detach();

    }

    /**
     * Reads a text file formatted by the GSL histogram outputting functions:
     * range[0] range[1] bin[0]
     * range[1] range[2] bin[1]
     * range[2] range[3] bin[2]
     * ....
     * range[n-1] range[n] bin[n-1]
     * 
     * Writes the results as a pair of XY values into an XYSeries object from FreeChart,
     * taking the lower end of the range for each pair
     * 
     * @param fileName
     *            The path to the text file to read the data from
     * @param series
     *            The series object to write the results into
     */
    public static void readHistogramIntoXYSeries(String fileName, XYSeries series) {
        final int nColumns = 3;
        // From http://www.java2s.com/Code/Java/File-Input-Output/ReadingNumbersfromaTextFile.htm
        try (Reader r = new BufferedReader(new FileReader(fileName))) {

            StreamTokenizer stok = new StreamTokenizer(r);
            stok.parseNumbers();
            int currentColumn = 0;
            double range0 = 0;
            stok.nextToken();

            while (stok.ttype != StreamTokenizer.TT_EOF) {
                if (stok.ttype == StreamTokenizer.TT_NUMBER) {
                    if (currentColumn == 0) {
                        range0 = stok.nval;
                    } else if (currentColumn == 2) {
                        series.add(range0, stok.nval);
                    }
                } else
                    Utils.log("Nonnumber while reading histogram from file " + fileName + ": " + stok.sval,
                            LogLevel.ERROR);
                stok.nextToken();
                currentColumn++;
                if (currentColumn == nColumns)
                    currentColumn = 0;
            }
        } catch (FileNotFoundException e) {
            throw new RuntimeException("Could not find file " + fileName + " from which to read histogram", e);
        } catch (IOException e) {
            printStack(e);
        }

    }

    /**
     * Reads a text file formatted by the GSL histogram outputting functions:
     * range[0] range[1] bin[0]
     * range[1] range[2] bin[1]
     * range[2] range[3] bin[2]
     * ....
     * range[n-1] range[n] bin[n-1]
     * 
     * Writes the results as a pair of XY values into an XYSeries object from FreeChart,
     * taking the lower end of the range for each pair
     * 
     * @param plot
     *            The plot produced by a plugin
     * @param series
     *            The series object to write the results into
     */
    public static void readHistogramIntoXYSeries(PluginIOCells plot, XYSeries series) {
        final int nColumns = 3;
        // From http://www.java2s.com/Code/Java/File-Input-Output/ReadingNumbersfromaTextFile.htm
        byte[] protobufArray = (byte[]) plot.getProperty("Protobuf");
        if (protobufArray == null)
            return;
        Reader r = new StringReader(new String(protobufArray));
        StreamTokenizer stok = new StreamTokenizer(r);
        stok.parseNumbers();
        int currentColumn = 0;
        double range0 = 0;
        try {
            stok.nextToken();

            while (stok.ttype != StreamTokenizer.TT_EOF) {
                if (stok.ttype == StreamTokenizer.TT_NUMBER) {
                    if (currentColumn == 0) {
                        range0 = stok.nval;
                    } else if (currentColumn == 2) {
                        series.add(range0, stok.nval);
                    }
                } else
                    Utils.log("Nonnumber while reading histogram from string " + plot.getProperty("Protobuf") + ": "
                            + stok.sval, LogLevel.ERROR);
                stok.nextToken();
                currentColumn++;
                if (currentColumn == nColumns)
                    currentColumn = 0;
            }
        } catch (IOException e) {
            printStack(e);
            return;
        }

    }

    public static String printPixelTypes(PixelType[] pixelTypes) {
        if (pixelTypes == null)
            return "";
        StringBuffer b = new StringBuffer(200);
        for (int i = 0; i < pixelTypes.length; i++) {
            b.append(pixelTypes[i]);
            if (i < pixelTypes.length - 1)
                b.append(", ");
        }
        return b.toString();
    }

    public static double mean(double[] array) {
        if (array.length == 0)
            throw new IllegalArgumentException("Cannot compute mean of empty array");
        double result = 0;
        for (double d : array) {
            result += d;
        }
        return result / array.length;
    }

    public static double median(double[] array) {
        if (array.length == 0)
            throw new IllegalArgumentException("Cannot compute median of empty array");
        double[] sortedArray = array.clone();
        Arrays.sort(sortedArray);
        return sortedArray[array.length / 2];
    }

    public static double stdDev(double[] array, Double mean) {
        if (array.length == 0)
            throw new IllegalArgumentException("Cannot compute stdev of empty array");
        double result = 0;
        if (mean == null)
            mean = mean(array);
        double localMean = mean;
        for (double d : array) {
            result += (d - localMean) * (d - localMean);
        }
        return Math.sqrt(result / array.length);
    }

    public static double confidence(double level, int n, double stdDev) {
        double multiplier = 0;
        if (level == 0.05) {
            multiplier = 1.96;
        } else if (level == 0.16) {
            multiplier = 1.405;
        } else
            throw new RuntimeException("Unimplemented confidence level");
        return multiplier * stdDev / (n);
    }

    public static float getMin(IPluginIOHyperstack imageSource) {
        float the_min = Float.MAX_VALUE;
        for (IPluginIOStack channel : imageSource.getChannels().values()) {
            float channelMinimum = getMin(channel);
            if (channelMinimum < the_min)
                the_min = channelMinimum;
        }
        return the_min;
    }

    public static float getMax(IPluginIOHyperstack imageSource) {
        float the_max = Float.MIN_VALUE;
        for (IPluginIOStack channel : imageSource.getChannels().values()) {
            float channelMaximum = getMax(channel);
            if (channelMaximum > the_max)
                the_max = channelMaximum;
        }
        return the_max;
    }

    public static void setVNCSettings(boolean selected) {
        VNCScrollWheel = selected;
    }

    public static void main(String[] args) throws IOException {// findStepsInProfile
        File profileFile = new File(args[0]);
        int columnIndex = Integer.parseInt(args[1]);
        int xColumnIndex = Integer.parseInt(args[2]);
        int nColumns = Integer.parseInt(args[3]);
        float windowLength = Float.parseFloat(args[4]);

        // FIXME Code duplicated in RegisterVideoLabelTimes plugin. Create a parsing class.
        try (Reader r = new BufferedReader(new FileReader(profileFile))) {

            ReaderTokenizer stok = new ReaderTokenizer(r);

            stok.parseNumbers();
            int currentColumn = 0;
            // Skip column headers
            for (int i = 0; i < nColumns; i++) {
                stok.nextToken();
            }
            stok.nextToken();

            // FloatList profileList=new ArrayFloatList(100000); //Time in seconds
            // FloatList xPositionList=new ArrayFloatList(100000); //Time in seconds

            ArrayList<float[]> readings = new ArrayList<>();

            int currentIndex = 0;
            readings.add(new float[2]);
            while (stok.ttype != ReaderTokenizer.TT_EOF) {

                if (stok.ttype == ReaderTokenizer.TT_NUMBER) {
                    float f = (float) stok.nval;
                    if (currentColumn == columnIndex) {
                        readings.get(currentIndex)[1] = f;
                        if (f < 0) {
                            Utils.log("Read negative profile", LogLevel.ERROR);
                        }
                    } else if (currentColumn == xColumnIndex) {
                        readings.get(currentIndex)[0] = f;
                        if (f < 0) {
                            Utils.log("Read negative profile", LogLevel.ERROR);
                        }
                    }
                } else
                    throw new RuntimeException(
                            "Unexpected read from detected cell file " + stok.sval + " of type " + stok.ttype);
                stok.nextToken();
                currentColumn++;
                if (currentColumn == nColumns) {
                    currentColumn = 0;
                    currentIndex++;
                    readings.add(new float[2]);
                }
            }

            readings.remove(readings.size() - 1);

            float[][] readingsArray = readings.toArray(new float[][] { { 1, 2 } });

            Arrays.sort(readingsArray, (o1, o2) -> Float.compare(o1[0], o2[0]));

            float[] steps = minMaxSlidingWindow(readingsArray, windowLength);

            for (int i = 0; i < steps.length; i++) {
                System.out.println((readingsArray[i][0] + windowLength * 0.5) + "\t" + steps[i]);
            }
        } catch (FileNotFoundException e) {
            throw new RuntimeException("Could not find output file to read times from", e);
        }
    }

    private static float[] minMaxSlidingWindow(float[][] numbers, float windowLength) {
        int windowStart = 0;

        float[] result = new float[numbers.length];
        int arrayIndex = 0;
        float globalMin = Float.MAX_VALUE;

        for (float[] number : numbers) {
            if (number[1] < globalMin)
                globalMin = number[1];
        }

        while (arrayIndex < numbers.length - 1) {
            int effectiveEnd = windowStart;
            while ((numbers[effectiveEnd][0] - numbers[windowStart][0] < windowLength)
                    && (effectiveEnd < numbers.length - 1))
                effectiveEnd++;

            float max = -Float.MAX_VALUE;
            float min = Float.MAX_VALUE;

            for (int i = windowStart; i < effectiveEnd; i++) {
                if (numbers[i][1] > max)
                    max = numbers[i][1];
                if (numbers[i][1] < min)
                    min = numbers[i][1];
            }

            if (min > globalMin)
                result[arrayIndex] = (max - min) / (min - globalMin);
            else
                result[arrayIndex] = Float.NaN;

            windowStart++;
            arrayIndex++;
        }

        return result;
    }

    public static Color colorFromString(String colorName) {
        Color result = null;
        try {
            Field field = Class.forName("java.awt.Color").getField(colorName.toLowerCase());
            result = (Color) field.get(null);
        } catch (Exception e) {
            Utils.log("Error trying to parse color " + colorName, LogLevel.ERROR);
        }
        return result;
    }

    public static double enhanceMin(double min) {
        return min - Math.abs(min) * 0.01;
    }

    public static double enhanceMax(double max) {
        return max + Math.abs(max) * 0.01;
    }

}