tracing.ShollAnalysisDialog.java Source code

Java tutorial

Introduction

Here is the source code for tracing.ShollAnalysisDialog.java

Source

/* -*- mode: java; c-basic-offset: 8; indent-tabs-mode: t; tab-width: 8 -*- */

/* Copyright 2006, 2007, 2008, 2009, 2010, 2011 Mark Longair */

/*
  This file is part of the ImageJ plugin "Simple Neurite Tracer".
    
  The ImageJ plugin "Simple Neurite Tracer" 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.
    
  The ImageJ plugin "Simple Neurite Tracer" 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.
    
  In addition, as a special exception, the copyright holders give
  you permission to combine this program with free software programs or
  libraries that are released under the Apache Public License.
    
  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

package tracing;

import ij.*;
import ij.io.*;

import java.awt.Dialog;
import java.awt.Insets;
import java.awt.Checkbox;
import java.awt.TextField;
import java.awt.Panel;
import java.awt.Rectangle;
import java.awt.Label;
import java.awt.CheckboxGroup;
import java.awt.event.ActionEvent;
import java.awt.event.ItemEvent;
import java.awt.event.WindowEvent;
import java.awt.event.TextEvent;
import java.awt.Button;
import java.awt.event.WindowListener;
import java.awt.event.TextListener;
import java.awt.event.ActionListener;
import java.awt.event.ItemListener;
import java.awt.BorderLayout;
import java.awt.GridBagLayout;
import java.awt.GridBagConstraints;
import java.awt.image.IndexColorModel;

import java.io.*;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import ij.gui.GUI;
import ij.measure.Calibration;
import ij.process.ShortProcessor;
import ij.process.ImageProcessor;
import ij.measure.ResultsTable;
import ij.plugin.filter.Analyzer;

import util.FindConnectedRegions;

import org.jfree.chart.JFreeChart;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.LogAxis;
import org.jfree.chart.renderer.xy.XYBarRenderer;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.chart.renderer.xy.StandardXYBarPainter;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JButton;

import org.w3c.dom.DOMImplementation;
import org.apache.batik.dom.GenericDOMImplementation;

import org.w3c.dom.Document;
import org.apache.batik.svggen.SVGGraphics2D;

import org.apache.commons.math.stat.regression.SimpleRegression;

@SuppressWarnings("serial")
public class ShollAnalysisDialog extends Dialog
        implements WindowListener, ActionListener, TextListener, ItemListener {

    protected double x_start, y_start, z_start;

    protected CheckboxGroup pathsGroup = new CheckboxGroup();
    protected Checkbox useAllPathsCheckbox = new Checkbox("Use all paths in analysis?", pathsGroup, false);
    protected Checkbox useSelectedPathsCheckbox = new Checkbox("Use only selected paths in analysis?", pathsGroup,
            true);
    protected Button makeShollImageButton = new Button("Make Sholl image");
    protected Button drawShollGraphButton = new Button("Draw Graph");
    protected Button exportDetailAsCSVButton = new Button("Export detailed results as CSV");
    protected Button exportSummaryAsCSVButton = new Button("Export summary results as CSV");
    protected Button addToResultsTableButton = new Button("Add to Results Table");

    protected int numberOfSelectedPaths;
    protected int numberOfAllPaths;

    protected CheckboxGroup axesGroup = new CheckboxGroup();
    protected Checkbox normalAxes = new Checkbox("Use standard axes", axesGroup, true);
    protected Checkbox semiLogAxes = new Checkbox("Use semi-log axes", axesGroup, false);
    protected Checkbox logLogAxes = new Checkbox("Use log-log axes", axesGroup, false);

    protected CheckboxGroup normalizationGroup = new CheckboxGroup();
    protected Checkbox noNormalization = new Checkbox("No normalization of intersections", normalizationGroup,
            true);
    protected Checkbox normalizationForSphereVolume = new Checkbox("[BUG: should be set in the constructor]",
            normalizationGroup, false);

    protected TextField sampleSeparation = new TextField(
            "" + Prefs.get("tracing.ShollAnalysisDialog.sampleSeparation", 0));

    protected ShollResults currentResults;
    protected ImagePlus originalImage;

    GraphFrame graphFrame;

    public void actionPerformed(ActionEvent e) {
        Object source = e.getSource();
        ShollResults results = null;
        synchronized (this) {
            results = getCurrentResults();
        }
        if (results == null) {
            IJ.error("The sphere separation field must be a number, not '" + sampleSeparation.getText() + "'");
            return;
        }
        if (source == makeShollImageButton) {
            results.makeShollCrossingsImagePlus(originalImage);
        } else if (source == exportDetailAsCSVButton || source == exportSummaryAsCSVButton) {

            boolean detail = (source == exportDetailAsCSVButton);

            SaveDialog sd = new SaveDialog("Export data as...",
                    "sholl-" + (detail ? "detail" : "summary") + results.getSuggestedSuffix(), ".csv");

            if (sd.getFileName() == null) {
                return;
            }

            File saveFile = new File(sd.getDirectory(), sd.getFileName());
            if ((saveFile != null) && saveFile.exists()) {
                if (!IJ.showMessageWithCancel("Export data...", "The file " + saveFile.getAbsolutePath()
                        + " already exists.\n" + "Do you want to replace it?"))
                    return;
            }

            IJ.showStatus("Exporting CSV data to " + saveFile.getAbsolutePath());

            try {
                if (detail)
                    results.exportDetailToCSV(saveFile);
                else
                    results.exportSummaryToCSV(saveFile);
            } catch (IOException ioe) {
                IJ.error("Saving to " + saveFile.getAbsolutePath() + " failed");
                return;
            }

        } else if (source == drawShollGraphButton) {
            graphFrame.setVisible(true);
        } else if (source == addToResultsTableButton) {
            results.addToResultsTable();
        }
    }

    public void textValueChanged(TextEvent e) {
        Object source = e.getSource();
        if (source == sampleSeparation) {
            String sampleSeparationText = sampleSeparation.getText();
            float s;
            try {
                s = Float.parseFloat(sampleSeparationText);
            } catch (NumberFormatException nfe) {
                return;
            }
            if (s >= 0)
                updateResults();
        }
    }

    protected synchronized void updateResults() {
        ShollResults results = getCurrentResults();
        resultsPanel.updateFromResults(results);
        JFreeChart chart = results.createGraph();
        if (chart == null)
            return;
        if (graphFrame == null)
            graphFrame = new GraphFrame(chart, results.getSuggestedSuffix());
        else
            graphFrame.updateWithNewChart(chart, results.getSuggestedSuffix());
    }

    public void itemStateChanged(ItemEvent e) {
        updateResults();
    }

    public ShollResults getCurrentResults() {
        List<ShollPoint> pointsToUse;
        String description = "Sholl analysis ";
        String postDescription = " for " + originalImage.getTitle();
        boolean useAllPaths = !useSelectedPathsCheckbox.getState();
        if (useAllPaths) {
            pointsToUse = shollPointsAllPaths;
            description += "of all paths" + postDescription;
        } else {
            pointsToUse = shollPointsSelectedPaths;
            description += "of selected paths " + postDescription;
        }

        int axes = 0;
        if (normalAxes.getState())
            axes = AXES_NORMAL;
        else if (semiLogAxes.getState())
            axes = AXES_SEMI_LOG;
        else if (logLogAxes.getState())
            axes = AXES_LOG_LOG;
        else
            throw new RuntimeException("BUG: somehow no axis checkbox was selected");

        int normalization = 0;
        if (noNormalization.getState())
            normalization = NOT_NORMALIZED;
        else if (normalizationForSphereVolume.getState())
            normalization = NORMALIZED_FOR_SPHERE_VOLUME;
        else
            throw new RuntimeException("BUG: somehow no normalization checkbox was selected");

        String sphereSeparationString = sampleSeparation.getText();
        double sphereSeparation = Double.MIN_VALUE;

        try {
            sphereSeparation = Double.parseDouble(sphereSeparationString);
        } catch (NumberFormatException nfe) {
            return null;
        }

        ShollResults results = new ShollResults(pointsToUse, originalImage, useAllPaths,
                useAllPaths ? numberOfAllPaths : numberOfSelectedPaths, x_start, y_start, z_start, description,
                axes, normalization, sphereSeparation, twoDimensional);

        return results;
    }

    public static class GraphFrame extends JFrame implements ActionListener {
        JButton exportButton;
        JFreeChart chart = null;
        ChartPanel chartPanel = null;
        JPanel mainPanel;
        String suggestedSuffix;

        public void updateWithNewChart(JFreeChart chart, String suggestedSuffix) {
            updateWithNewChart(chart, suggestedSuffix, false);
        }

        synchronized public void updateWithNewChart(JFreeChart chart, String suggestedSuffix, boolean setSize) {
            this.suggestedSuffix = suggestedSuffix;
            if (chartPanel != null)
                remove(chartPanel);
            chartPanel = null;
            this.chart = chart;
            chartPanel = new ChartPanel(chart);
            if (setSize)
                chartPanel.setPreferredSize(new java.awt.Dimension(800, 600));
            mainPanel.add(chartPanel, BorderLayout.CENTER);
            validate();
        }

        public GraphFrame(JFreeChart chart, String suggestedSuffix) {
            super();

            this.suggestedSuffix = suggestedSuffix;

            mainPanel = new JPanel();
            mainPanel.setLayout(new BorderLayout());

            updateWithNewChart(chart, suggestedSuffix, true);

            JPanel buttonsPanel = new JPanel();
            exportButton = new JButton("Export graph as SVG");
            exportButton.addActionListener(this);
            buttonsPanel.add(exportButton);
            mainPanel.add(buttonsPanel, BorderLayout.SOUTH);

            setContentPane(mainPanel);
            validate();
            setSize(new java.awt.Dimension(500, 270));
            GUI.center(this);
        }

        public void actionPerformed(ActionEvent e) {
            Object source = e.getSource();
            if (source == exportButton) {
                exportGraphAsSVG();
            }
        }

        public void exportGraphAsSVG() {

            SaveDialog sd = new SaveDialog("Export graph as...", "sholl" + suggestedSuffix, ".svg");

            if (sd.getFileName() == null) {
                return;
            }

            File saveFile = new File(sd.getDirectory(), sd.getFileName());
            if ((saveFile != null) && saveFile.exists()) {
                if (!IJ.showMessageWithCancel("Export graph...", "The file " + saveFile.getAbsolutePath()
                        + " already exists.\n" + "Do you want to replace it?"))
                    return;
            }

            IJ.showStatus("Exporting graph to " + saveFile.getAbsolutePath());

            try {
                exportChartAsSVG(chart, chartPanel.getBounds(), saveFile);
            } catch (IOException ioe) {
                IJ.error("Saving to " + saveFile.getAbsolutePath() + " failed");
                return;
            }

        }

        /**
         * Exports a JFreeChart to a SVG file.
         *
         * @param chart JFreeChart to export
         * @param bounds the dimensions of the viewport
         * @param svgFile the output file.
         * @throws IOException if writing the svgFile fails.
            
         * This method is taken from:
         *    http://dolf.trieschnigg.nl/jfreechart/
         */
        void exportChartAsSVG(JFreeChart chart, Rectangle bounds, File svgFile) throws IOException {

            // Get a DOMImplementation and create an XML document
            DOMImplementation domImpl = GenericDOMImplementation.getDOMImplementation();
            Document document = domImpl.createDocument(null, "svg", null);

            // Create an instance of the SVG Generator
            SVGGraphics2D svgGenerator = new SVGGraphics2D(document);

            // draw the chart in the SVG generator
            chart.draw(svgGenerator, bounds);

            // Write svg file
            OutputStream outputStream = new FileOutputStream(svgFile);
            Writer out = new OutputStreamWriter(outputStream, "UTF-8");
            svgGenerator.stream(out, true /* use css */);
            outputStream.flush();
            outputStream.close();
        }
    }

    public static final int AXES_NORMAL = 1;
    public static final int AXES_SEMI_LOG = 2;
    public static final int AXES_LOG_LOG = 3;

    public static final String[] axesParameters = { null, "normal", "semi-log", "log-log" };

    public static final int NOT_NORMALIZED = 1;
    public static final int NORMALIZED_FOR_SPHERE_VOLUME = 2;

    public static final String[] normalizationParameters = { null, "not-normalized", "normalized" };

    public static class ShollResults {
        protected double[] squaredRangeStarts;
        protected int[] crossingsPastEach;
        protected int n;
        protected double x_start, y_start, z_start;
        /* maxCrossings is the same as the "Dendrite Maximum". */
        protected int maxCrossings = Integer.MIN_VALUE;
        protected double criticalValue = Double.MIN_VALUE;
        protected String description;
        protected int axes;
        protected int normalization;
        protected double sphereSeparation;
        protected double[] x_graph_points;
        protected double[] y_graph_points;
        protected double minY;
        protected double maxY;
        protected int graphPoints;
        protected String yAxisLabel;
        protected String xAxisLabel;
        protected double regressionGradient = Double.MIN_VALUE;
        protected double regressionIntercept = Double.MIN_VALUE;
        String parametersSuffix;

        public int getDendriteMaximum() {
            return maxCrossings;
        }

        public double getCriticalValue() {
            return criticalValue;
        }

        public double getRegressionGradient() {
            return regressionGradient;
        }

        public double getShollRegressionCoefficient() {
            return -regressionGradient;
        }

        public double getRegressionIntercept() {
            return regressionIntercept;
        }

        public double getMaxDistanceSquared() {
            return squaredRangeStarts[n - 1];
        }

        public String getSuggestedSuffix() {
            return parametersSuffix;
        }

        public void addToResultsTable() {
            ResultsTable rt = Analyzer.getResultsTable();
            String[] headings = {};
            rt.setHeading(0, "Filename");
            rt.setHeading(1, "AllPathsUsed");
            rt.setHeading(2, "NumberOfPathsUsed");
            rt.setHeading(3, "SphereSeparation");
            rt.setHeading(4, "Normalization");
            rt.setHeading(5, "Axes");
            rt.setHeading(6, "CriticalValue");
            rt.setHeading(7, "DendriteMaximum");
            rt.setHeading(8, "ShollRegressionCoefficient");
            rt.setHeading(9, "RegressionGradient");
            rt.setHeading(10, "RegressionIntercept");
            rt.incrementCounter();
            rt.addLabel("Filename", getOriginalFilename());
            rt.addValue("AllPathsUsed", useAllPaths ? 1 : 0);
            rt.addValue("NumberOfPathsUsed", numberOfPathsUsed);
            rt.addValue("SphereSeparation", sphereSeparation);
            rt.addValue("Normalization", normalization);
            rt.addValue("Axes", axes);
            rt.addValue("CriticalValue", getCriticalValue());
            rt.addValue("DendriteMaximum", getDendriteMaximum());
            rt.addValue("ShollRegressionCoefficient", getShollRegressionCoefficient());
            rt.addValue("RegressionGradient", getRegressionGradient());
            rt.addValue("RegressionIntercept", getRegressionIntercept());
            rt.show("Results");
        }

        boolean twoDimensional;
        ImagePlus originalImage;
        boolean useAllPaths;
        int numberOfPathsUsed;

        public ShollResults(List<ShollPoint> shollPoints, ImagePlus originalImage, boolean useAllPaths,
                int numberOfPathsUsed, double x_start, double y_start, double z_start, String description, int axes,
                int normalization, double sphereSeparation, boolean twoDimensional) {
            parametersSuffix = "_" + axesParameters[axes] + "_" + normalizationParameters[normalization] + "_"
                    + sphereSeparation;
            this.originalImage = originalImage;
            this.useAllPaths = useAllPaths;
            this.numberOfPathsUsed = numberOfPathsUsed;
            this.x_start = x_start;
            this.y_start = y_start;
            this.z_start = z_start;
            this.description = description;
            this.axes = axes;
            this.normalization = normalization;
            this.sphereSeparation = sphereSeparation;
            this.twoDimensional = twoDimensional;
            Collections.sort(shollPoints);
            n = shollPoints.size();
            squaredRangeStarts = new double[n];
            crossingsPastEach = new int[n];
            int currentCrossings = 0;
            for (int i = 0; i < n; ++i) {
                ShollPoint p = shollPoints.get(i);
                if (p.nearer)
                    ++currentCrossings;
                else
                    --currentCrossings;
                squaredRangeStarts[i] = p.distanceSquared;
                crossingsPastEach[i] = currentCrossings;
                if (currentCrossings > maxCrossings) {
                    maxCrossings = currentCrossings;
                    criticalValue = Math.sqrt(p.distanceSquared);
                }
                // System.out.println("Range starting at: "+Math.sqrt(p.distanceSquared)+" has crossings: "+currentCrossings);
            }
            xAxisLabel = "Distance in space from ( " + x_start + ", " + y_start + ", " + z_start + " )";
            yAxisLabel = "Number of intersections";
            if (sphereSeparation > 0) {
                graphPoints = (int) Math.ceil(Math.sqrt(getMaxDistanceSquared()) / sphereSeparation);
                x_graph_points = new double[graphPoints];
                y_graph_points = new double[graphPoints];
                for (int i = 0; i < graphPoints; ++i) {
                    double x = i * sphereSeparation;
                    x_graph_points[i] = x;
                    double distanceSquared = x * x;
                    y_graph_points[i] = crossingsAtDistanceSquared(distanceSquared);
                }
            } else {
                graphPoints = n;
                x_graph_points = new double[n];
                y_graph_points = new double[n];
                for (int i = 0; i < graphPoints; ++i) {
                    double distanceSquared = squaredRangeStarts[i];
                    double x = Math.sqrt(distanceSquared);
                    x_graph_points[i] = x;
                    y_graph_points[i] = crossingsAtDistanceSquared(distanceSquared);
                }
            }
            if (normalization == NORMALIZED_FOR_SPHERE_VOLUME) {
                for (int i = 0; i < graphPoints; ++i) {
                    double x;
                    if (sphereSeparation > 0) {
                        x = x_graph_points[i];
                    } else {
                        double startX = x_graph_points[i];
                        double endX;
                        if (i < graphPoints - 1)
                            endX = x_graph_points[i + 1];
                        else
                            endX = x_graph_points[i];
                        x = (startX + endX) / 2;
                    }
                    double distanceSquared = x * x;
                    if (twoDimensional)
                        y_graph_points[i] /= (Math.PI * distanceSquared);
                    else
                        y_graph_points[i] /= ((4.0 * Math.PI * x * distanceSquared) / 3.0);
                }
                if (twoDimensional)
                    xAxisLabel += " / area enclosed by circle";
                else
                    yAxisLabel += " / volume enclosed by sphere";
            }

            SimpleRegression regression = new SimpleRegression();

            maxY = Double.MIN_VALUE;
            minY = Double.MAX_VALUE;
            for (int i = 0; i < graphPoints; ++i) {
                double x = x_graph_points[i];
                double y = y_graph_points[i];
                double x_for_regression = x;
                double y_for_regression = y;
                if (!(Double.isInfinite(y) || Double.isNaN(y))) {
                    if (y > maxY)
                        maxY = y;
                    if (y < minY)
                        minY = y;
                    if (axes == AXES_SEMI_LOG) {
                        if (y <= 0)
                            continue;
                        y_for_regression = Math.log(y);
                    } else if (axes == AXES_LOG_LOG) {
                        if (x <= 0 || y <= 0)
                            continue;
                        x_for_regression = Math.log(x);
                        y_for_regression = Math.log(y);
                    }
                    regression.addData(x_for_regression, y_for_regression);
                }
            }
            regressionGradient = regression.getSlope();
            regressionIntercept = regression.getIntercept();

            if (maxY == Double.MIN_VALUE)
                throw new RuntimeException("[BUG] Somehow there were no valid points found");
        }

        public JFreeChart createGraph() {

            XYSeriesCollection data = null;

            double minX = Double.MAX_VALUE;
            double maxX = Double.MIN_VALUE;

            final XYSeries series = new XYSeries("Intersections");
            for (int i = 0; i < graphPoints; ++i) {
                double x = x_graph_points[i];
                double y = y_graph_points[i];
                if (Double.isInfinite(y) || Double.isNaN(y))
                    continue;
                if (axes == AXES_SEMI_LOG || axes == AXES_LOG_LOG) {
                    if (y <= 0)
                        continue;
                }
                if (axes == AXES_LOG_LOG) {
                    if (x <= 0)
                        continue;
                }
                if (x < minX)
                    minX = x;
                if (x > maxX)
                    maxX = x;
                series.add(x, y);
            }
            data = new XYSeriesCollection(series);

            ValueAxis xAxis = null;
            ValueAxis yAxis = null;
            if (axes == AXES_NORMAL) {
                xAxis = new NumberAxis(xAxisLabel);
                yAxis = new NumberAxis(yAxisLabel);
            } else if (axes == AXES_SEMI_LOG) {
                xAxis = new NumberAxis(xAxisLabel);
                yAxis = new LogAxis(yAxisLabel);
            } else if (axes == AXES_LOG_LOG) {
                xAxis = new LogAxis(xAxisLabel);
                yAxis = new LogAxis(yAxisLabel);
            }

            xAxis.setRange(minX, maxX);
            if (axes == AXES_NORMAL)
                yAxis.setRange(0, maxY);
            else
                yAxis.setRange(minY, maxY);

            XYItemRenderer renderer = null;
            if (sphereSeparation > 0) {
                renderer = new XYLineAndShapeRenderer();
            } else {
                XYBarRenderer barRenderer = new XYBarRenderer();
                barRenderer.setShadowVisible(false);
                barRenderer.setGradientPaintTransformer(null);
                barRenderer.setDrawBarOutline(false);
                barRenderer.setBarPainter(new StandardXYBarPainter());
                renderer = barRenderer;
            }

            XYPlot plot = new XYPlot(data, xAxis, yAxis, renderer);

            return new JFreeChart(description, plot);
        }

        public int crossingsAtDistanceSquared(double distanceSquared) {

            int minIndex = 0;
            int maxIndex = n - 1;

            if (distanceSquared < squaredRangeStarts[minIndex])
                return 1;
            else if (distanceSquared > squaredRangeStarts[maxIndex])
                return 0;

            while (maxIndex - minIndex > 1) {

                int midPoint = (maxIndex + minIndex) / 2;

                if (distanceSquared < squaredRangeStarts[midPoint])
                    maxIndex = midPoint;
                else
                    minIndex = midPoint;
            }
            return crossingsPastEach[minIndex];
        }

        public ImagePlus makeShollCrossingsImagePlus(ImagePlus original) {
            int width = original.getWidth();
            int height = original.getHeight();
            int depth = original.getStackSize();
            Calibration c = original.getCalibration();
            double x_spacing = 1;
            double y_spacing = 1;
            double z_spacing = 1;
            if (c != null) {
                x_spacing = c.pixelWidth;
                y_spacing = c.pixelHeight;
                z_spacing = c.pixelDepth;
            }
            ImageStack stack = new ImageStack(width, height);
            for (int z = 0; z < depth; ++z) {
                short[] pixels = new short[width * height];
                for (int y = 0; y < height; ++y) {
                    for (int x = 0; x < width; ++x) {
                        double xdiff = x_spacing * x - x_start;
                        double ydiff = y_spacing * y - y_start;
                        double zdiff = z_spacing * z - z_start;
                        double distanceSquared = xdiff * xdiff + ydiff * ydiff + zdiff * zdiff;
                        pixels[y * width + x] = (short) crossingsAtDistanceSquared(distanceSquared);
                    }
                }
                ShortProcessor sp = new ShortProcessor(width, height);
                sp.setPixels(pixels);
                stack.addSlice("", sp);
            }
            ImagePlus result = new ImagePlus(description, stack);
            result.show();
            IndexColorModel icm = FindConnectedRegions.backgroundAndSpectrum(255);
            stack.setColorModel(icm);
            ImageProcessor ip = result.getProcessor();
            if (ip != null) {
                ip.setColorModel(icm);
                ip.setMinAndMax(0, maxCrossings);
            }
            result.updateAndDraw();

            if (c != null)
                result.setCalibration(c);
            return result;
        }

        public static void csvQuoteAndPrint(PrintWriter pw, Object o) {
            pw.print(PathAndFillManager.stringForCSV("" + o));
        }

        public String getOriginalFilename() {
            FileInfo originalFileInfo = originalImage.getOriginalFileInfo();
            if (originalFileInfo.directory == null)
                return "[unknown]";
            else
                return new File(originalFileInfo.directory, originalFileInfo.fileName).getAbsolutePath();

        }

        public void exportSummaryToCSV(File outputFile) throws IOException {
            String[] headers = new String[] { "Filename", "AllPathsUsed", "NumberOfPathsUsed", "SphereSeparation",
                    "Normlization", "Axes", "CriticalValue", "DendriteMaximum", "ShollRegressionCoefficient",
                    "RegressionGradient", "RegressionIntercept" };

            PrintWriter pw = new PrintWriter(
                    new OutputStreamWriter(new FileOutputStream(outputFile.getAbsolutePath()), "UTF-8"));
            int columns = headers.length;
            for (int c = 0; c < columns; ++c) {
                csvQuoteAndPrint(pw, headers[c]);
                if (c < (columns - 1))
                    pw.print(",");
            }
            pw.print("\r\n");
            csvQuoteAndPrint(pw, getOriginalFilename());
            pw.print(",");
            csvQuoteAndPrint(pw, useAllPaths);
            pw.print(",");
            csvQuoteAndPrint(pw, numberOfPathsUsed);
            pw.print(",");
            csvQuoteAndPrint(pw, sphereSeparation);
            pw.print(",");
            csvQuoteAndPrint(pw, normalizationParameters[normalization]);
            pw.print(",");
            csvQuoteAndPrint(pw, axesParameters[axes]);
            pw.print(",");
            csvQuoteAndPrint(pw, getCriticalValue());
            pw.print(",");
            csvQuoteAndPrint(pw, getDendriteMaximum());
            pw.print(",");
            csvQuoteAndPrint(pw, getShollRegressionCoefficient());
            pw.print(",");
            csvQuoteAndPrint(pw, getRegressionGradient());
            pw.print(",");
            csvQuoteAndPrint(pw, getRegressionIntercept());
            pw.print("\r\n");

            pw.close();
        }

        public void exportDetailToCSV(File outputFile) throws IOException {
            String[] headers;
            if (sphereSeparation > 0)
                headers = new String[] { "Radius", "Crossings", "NormalizedCrossings" };
            else
                headers = new String[] { "StartRadius", "EndRadius", "Crossings", "NormalizedCrossings" };

            PrintWriter pw = new PrintWriter(
                    new OutputStreamWriter(new FileOutputStream(outputFile.getAbsolutePath()), "UTF-8"));
            int columns = headers.length;
            for (int c = 0; c < columns; ++c) {
                csvQuoteAndPrint(pw, headers[c]);
                if (c < (columns - 1))
                    pw.print(",");
            }
            pw.print("\r\n");
            if (sphereSeparation > 0) {
                int graphPoints = (int) Math.ceil(Math.sqrt(getMaxDistanceSquared()) / sphereSeparation);
                for (int i = 0; i < graphPoints; ++i) {
                    double x = i * sphereSeparation;
                    double distanceSquared = x * x;
                    int crossings = crossingsAtDistanceSquared(distanceSquared);
                    double normalizedCrossings = -Double.MIN_VALUE;
                    if (twoDimensional)
                        normalizedCrossings = crossings / (Math.PI * distanceSquared);
                    else
                        normalizedCrossings = crossings / ((4.0 * Math.PI * x * distanceSquared) / 3.0);
                    csvQuoteAndPrint(pw, x);
                    pw.print(",");
                    csvQuoteAndPrint(pw, crossings);
                    pw.print(",");
                    csvQuoteAndPrint(pw, normalizedCrossings);
                    pw.print("\r\n");
                }
            } else {
                for (int i = 0; i < (n - 1); ++i) {
                    double startXSquared = squaredRangeStarts[i];
                    double endXSquared = squaredRangeStarts[i + 1];
                    // Omit the empty ranges, since they're not likely to matter to anyone:
                    if (endXSquared == startXSquared)
                        continue;
                    double startX = Math.sqrt(startXSquared);
                    double endX = Math.sqrt(endXSquared);
                    double midX = (startX + endX) / 2;
                    int crossings = crossingsAtDistanceSquared(midX * midX);
                    double normalizedCrossings = -Double.MIN_VALUE;
                    if (twoDimensional)
                        normalizedCrossings = crossings / (Math.PI * (midX * midX));
                    else
                        normalizedCrossings = crossings / ((4.0 * Math.PI * (midX * midX * midX)) / 3.0);
                    csvQuoteAndPrint(pw, startX);
                    pw.print(",");
                    csvQuoteAndPrint(pw, endX);
                    pw.print(",");
                    csvQuoteAndPrint(pw, crossings);
                    pw.print(",");
                    csvQuoteAndPrint(pw, normalizedCrossings);
                    pw.print("\r\n");
                }
            }
            pw.close();
        }
    }

    public static class ShollPoint implements Comparable<ShollPoint> {
        protected boolean nearer;
        protected double distanceSquared;

        public int compareTo(ShollPoint other) {
            return Double.compare(this.distanceSquared, other.distanceSquared);
        }

        ShollPoint(double distanceSquared, boolean nearer) {
            this.distanceSquared = distanceSquared;
            this.nearer = nearer;
        }
    }

    public static void addPathPointsToShollList(Path p, double x_start, double y_start, double z_start,
            List<ShollPoint> shollPointsList) {

        for (int i = 0; i < p.points - 1; ++i) {
            double xdiff_first = p.precise_x_positions[i] - x_start;
            double ydiff_first = p.precise_y_positions[i] - y_start;
            double zdiff_first = p.precise_z_positions[i] - z_start;
            double xdiff_second = p.precise_x_positions[i + 1] - x_start;
            double ydiff_second = p.precise_y_positions[i + 1] - y_start;
            double zdiff_second = p.precise_z_positions[i + 1] - z_start;
            double distanceSquaredFirst = xdiff_first * xdiff_first + ydiff_first * ydiff_first
                    + zdiff_first * zdiff_first;
            double distanceSquaredSecond = xdiff_second * xdiff_second + ydiff_second * ydiff_second
                    + zdiff_second * zdiff_second;
            shollPointsList.add(new ShollPoint(distanceSquaredFirst, distanceSquaredFirst < distanceSquaredSecond));
            shollPointsList
                    .add(new ShollPoint(distanceSquaredSecond, distanceSquaredFirst >= distanceSquaredSecond));
        }

    }

    ArrayList<ShollPoint> shollPointsAllPaths;
    ArrayList<ShollPoint> shollPointsSelectedPaths;

    ResultsPanel resultsPanel = new ResultsPanel();

    protected boolean twoDimensional;

    public ShollAnalysisDialog(String title, double x_start, double y_start, double z_start,
            PathAndFillManager pafm, ImagePlus originalImage) {

        super(IJ.getInstance(), title, false);

        this.x_start = x_start;
        this.y_start = y_start;
        this.z_start = z_start;

        this.originalImage = originalImage;
        twoDimensional = (originalImage.getStackSize() == 1);

        shollPointsAllPaths = new ArrayList<ShollPoint>();
        shollPointsSelectedPaths = new ArrayList<ShollPoint>();

        numberOfAllPaths = 0;
        numberOfSelectedPaths = 0;

        for (Path p : pafm.allPaths) {
            boolean selected = p.getSelected();
            if (p.getUseFitted()) {
                p = p.fitted;
            } else if (p.fittedVersionOf != null)
                continue;
            addPathPointsToShollList(p, x_start, y_start, z_start, shollPointsAllPaths);
            ++numberOfAllPaths;
            if (selected) {
                addPathPointsToShollList(p, x_start, y_start, z_start, shollPointsSelectedPaths);
                ++numberOfSelectedPaths;
            }
        }

        useAllPathsCheckbox.setLabel("Use all " + numberOfAllPaths + " paths in analysis?");
        useSelectedPathsCheckbox.setLabel("Use only the " + numberOfSelectedPaths + " selected paths in analysis?");

        addWindowListener(this);

        setLayout(new GridBagLayout());

        GridBagConstraints c = new GridBagConstraints();

        c.gridx = 0;
        c.gridy = 0;
        c.anchor = GridBagConstraints.LINE_START;
        int margin = 10;
        c.insets = new Insets(margin, margin, 0, margin);
        useAllPathsCheckbox.addItemListener(this);
        add(useAllPathsCheckbox, c);

        ++c.gridy;
        c.insets = new Insets(0, margin, margin, margin);
        add(useSelectedPathsCheckbox, c);
        useSelectedPathsCheckbox.addItemListener(this);

        ++c.gridy;
        c.insets = new Insets(margin, margin, 0, margin);
        add(normalAxes, c);
        normalAxes.addItemListener(this);
        ++c.gridy;
        c.insets = new Insets(0, margin, 0, margin);
        add(semiLogAxes, c);
        semiLogAxes.addItemListener(this);
        ++c.gridy;
        c.insets = new Insets(0, margin, margin, margin);
        add(logLogAxes, c);
        logLogAxes.addItemListener(this);

        ++c.gridy;
        c.insets = new Insets(margin, margin, 0, margin);
        add(noNormalization, c);
        noNormalization.addItemListener(this);
        ++c.gridy;
        c.insets = new Insets(0, margin, margin, margin);
        if (twoDimensional)
            normalizationForSphereVolume.setLabel("Normalize for area enclosed by circle");
        else
            normalizationForSphereVolume.setLabel("Normalize for volume enclosed by circle");
        add(normalizationForSphereVolume, c);
        normalizationForSphereVolume.addItemListener(this);

        ++c.gridy;
        c.gridx = 0;
        Panel separationPanel = new Panel();
        separationPanel.add(new Label("Circle / sphere separation (0 for unsampled analysis)"));
        sampleSeparation.addTextListener(this);
        separationPanel.add(sampleSeparation);
        add(separationPanel, c);

        c.gridx = 0;
        ++c.gridy;
        c.insets = new Insets(margin, margin, margin, margin);
        add(resultsPanel, c);

        ++c.gridy;
        Panel buttonsPanel = new Panel();
        buttonsPanel.setLayout(new BorderLayout());
        Panel topRow = new Panel();
        Panel middleRow = new Panel();
        Panel bottomRow = new Panel();

        topRow.add(makeShollImageButton);
        makeShollImageButton.addActionListener(this);

        topRow.add(drawShollGraphButton);
        drawShollGraphButton.addActionListener(this);

        middleRow.add(exportDetailAsCSVButton);
        exportDetailAsCSVButton.addActionListener(this);

        middleRow.add(exportSummaryAsCSVButton);
        exportSummaryAsCSVButton.addActionListener(this);

        bottomRow.add(addToResultsTableButton);
        addToResultsTableButton.addActionListener(this);

        buttonsPanel.add(topRow, BorderLayout.NORTH);
        buttonsPanel.add(middleRow, BorderLayout.CENTER);
        buttonsPanel.add(bottomRow, BorderLayout.SOUTH);

        add(buttonsPanel, c);

        pack();

        GUI.center(this);
        setVisible(true);

        updateResults();
    }

    public class ResultsPanel extends Panel {
        Label headingLabel = new Label("Results:");
        String defaultText = "[Not calculated yet]";
        Label criticalValuesLabel = new Label(defaultText);
        Label dendriteMaximumLabel = new Label(defaultText);
        // Label schoenenRamificationIndexLabel = new Label(defaultText);
        Label shollsRegressionCoefficientLabel = new Label(defaultText);
        Label shollsRegressionInterceptLabel = new Label(defaultText);

        public ResultsPanel() {
            super();
            setLayout(new GridBagLayout());
            GridBagConstraints c = new GridBagConstraints();
            c.anchor = GridBagConstraints.LINE_START;
            c.gridx = 0;
            c.gridy = 0;
            c.gridwidth = 2;
            add(headingLabel, c);
            c.anchor = GridBagConstraints.LINE_END;
            c.gridx = 0;
            ++c.gridy;
            c.gridwidth = 1;
            add(new Label("Critical value(s):"), c);
            c.gridx = 1;
            add(criticalValuesLabel, c);
            c.gridx = 0;
            ++c.gridy;
            add(new Label("Dendrite maximum:"), c);
            c.gridx = 1;
            add(dendriteMaximumLabel, c);
            // c.gridx = 0;
            // ++ c.gridy;
            // add(new Label("Schoenen Ramification Index:"),c);
            // c.gridx = 1;
            // add(schoenenRamificationIndexLabel,c);
            c.gridx = 0;
            ++c.gridy;
            add(new Label("Sholl's Regression Coefficient:"), c);
            c.gridx = 1;
            add(shollsRegressionCoefficientLabel, c);
            c.gridx = 0;
            ++c.gridy;
            add(new Label("Sholl's Regression Intercept:"), c);
            c.gridx = 1;
            add(shollsRegressionInterceptLabel, c);
        }

        public void updateFromResults(ShollResults results) {
            dendriteMaximumLabel.setText("" + results.getDendriteMaximum());
            criticalValuesLabel.setText("" + results.getCriticalValue());
            shollsRegressionCoefficientLabel.setText("" + results.getShollRegressionCoefficient());
            shollsRegressionInterceptLabel.setText("" + results.getRegressionIntercept());
        }
    }

    public void windowClosing(WindowEvent e) {
        dispose();
    }

    public void windowActivated(WindowEvent e) {
    }

    public void windowDeactivated(WindowEvent e) {
    }

    public void windowClosed(WindowEvent e) {
    }

    public void windowOpened(WindowEvent e) {
    }

    public void windowIconified(WindowEvent e) {
    }

    public void windowDeiconified(WindowEvent e) {
    }

}