de.biomedical_imaging.ij.steger.Lines_.java Source code

Java tutorial

Introduction

Here is the source code for de.biomedical_imaging.ij.steger.Lines_.java

Source

/*
 * #%L
 * Ridge Detection plugin for ImageJ
 * %%
 * Copyright (C) 2014 - 2015 Thorsten Wagner (ImageJ java plugin), 1996-1998 Carsten Steger (original C code), 1999 R. Balasubramanian (detect lines code to incorporate within GRASP)
 * %%
 * This program 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 2 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public
 * License along with this program.  If not, see
 * <http://www.gnu.org/licenses/gpl-2.0.html>.
 * #L%
 */

package de.biomedical_imaging.ij.steger;

import java.awt.AWTEvent;
import java.awt.Color;
import java.awt.Font;
import java.awt.TextField;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

import ij.IJ;
import ij.ImagePlus;
import ij.Prefs;
import ij.gui.DialogListener;
import ij.gui.GenericDialog;
import ij.gui.Overlay;
import ij.gui.PointRoi;
import ij.gui.PolygonRoi;
import ij.gui.Roi;
import ij.gui.TextRoi;
import ij.measure.ResultsTable;
import ij.plugin.filter.ExtendedPlugInFilter;
import ij.plugin.filter.PlugInFilterRunner;
import ij.plugin.frame.RoiManager;
import ij.process.FloatPolygon;
import ij.process.ImageProcessor;

public class Lines_ implements ExtendedPlugInFilter, DialogListener {
    final static double lineWidthDefault = 3.5;
    double lineWidth = lineWidthDefault;

    final static double contrastHighDefault = 230;
    double contrastHigh = contrastHighDefault;

    final static double contrastLowDefault = 87;
    double contrastLow = contrastLowDefault;

    final static double sigmaDefault = 1.51;
    double sigma = sigmaDefault;

    final static double lowerThreshDefault = 3.06;
    double lowerThresh = lowerThreshDefault;

    final static double upperThreshDefault = 7.99;
    double upperThresh = upperThreshDefault;

    final static boolean isDarkLineDefault = false;
    boolean isDarkLine = isDarkLineDefault;

    final static boolean doCorrectPositionDefault = false;
    boolean doCorrectPosition = doCorrectPositionDefault;

    final static boolean doEstimateWidthDefault = false;
    boolean doEstimateWidth = doEstimateWidthDefault;

    final static boolean doExtendLineDefault = true;
    boolean doExtendLine = doExtendLineDefault;

    final static boolean showJunctionPointsDefault = false;
    boolean showJunctionPoints = showJunctionPointsDefault;

    final static boolean displayResultsDefault = true;
    boolean displayResults = displayResultsDefault;

    final static boolean addToRoiManagerDefault = true;
    boolean addToRoiManager = addToRoiManagerDefault;

    OverlapOption overlapOption = OverlapOption.NONE;

    final static boolean showIDsDefault = false;
    boolean showIDs = showIDsDefault;

    final static boolean verboseDefault = false;
    boolean verbose = verboseDefault;

    boolean isPreview = false;
    boolean contrastOrLineWidthChangedOnce = false;
    boolean doStack = false;

    private Options usedOptions = null;
    private static Lines_ instance = null;

    /** For each frame an ArrayList with the lines of a single frame **/// 
    ArrayList<Lines> result;

    /** For each frame an ArrayList with the junctions of a single frame **/
    ArrayList<Junctions> resultJunction;

    ImagePlus imp;

    public Lines_() {
        instance = this;
    }

    /**
     * Access the results from an external plugin
     * @return
     */
    public static Lines_ getInstance() {
        return instance;
    }

    @Override
    public int setup(String arg, ImagePlus imp) {
        if (arg.equals("final")) {
            sortLists();
            //assignLinesToJunctions();
            displayContours();
            if (displayResults) {
                createResultsTable(true);
            }
            if (addToRoiManager) {
                addToRoiManager();
            }
            return DONE;

        }
        //Check if the apache commons lang library is available
        try {
            Class.forName("org.apache.commons.lang3.mutable.MutableLong");
        } catch (ClassNotFoundException exception) {
            IJ.error("Please install apache-commons-lang 3",
                    "It seems that the apache-commons-lang 3 library is not installed on your system. \n "
                            + "Download the jar file under https://commons.apache.org/proper/commons-lang/ and copy it to plugins/jars");
            return DONE;
        }

        // Abort macro if no image is open
        if (imp == null) {
            IJ.error("No image open");
            return DONE;
        }

        /*
         * Because the optional parameters are easier to determine when the image has an non-inverted LUT, 
         * images with inverted LUT are reset.
         */
        if (imp.isInvertedLut()) {
            IJ.showMessage("LUT reset",
                    "The LUT of your image is inverted and will now reset for better parameter selection");
            IJ.run(imp, "Invert LUT", "");
        }

        Line.resetCounter();
        this.imp = imp;
        result = new ArrayList<Lines>();
        resultJunction = new ArrayList<Junctions>();
        readSettings();
        return DOES_8G + DOES_STACKS + FINAL_PROCESSING + PARALLELIZE_STACKS;
    }

    private void sortLists() {

        Collections.sort(result, new Comparator<Lines>() {

            @Override
            public int compare(Lines o1, Lines o2) {
                // TODO Auto-generated method stub
                if (o1.getFrame() < o2.getFrame())
                    return -1;
                if (o1.getFrame() > o2.getFrame())
                    return 1;
                return 0;
            }
        });
        Collections.sort(resultJunction, new Comparator<Junctions>() {

            @Override
            public int compare(Junctions o1, Junctions o2) {
                if (o1.getFrame() < o2.getFrame())
                    return -1;
                if (o1.getFrame() > o2.getFrame())
                    return 1;
                return 0;
            }
        });
    }

    @Override
    public int showDialog(ImagePlus imp, String command, PlugInFilterRunner pfr) {

        GenericDialogPlus gd = new GenericDialogPlus("Ridge Detection");
        gd.addMessage("Optional_parameters:");
        gd.addNumericField("Line_width", lineWidth, 1);
        gd.addNumericField("High_Contrast", contrastHigh, 0);
        gd.addNumericField("Low_Contrast", contrastLow, 0);
        gd.addMessage("Mandatory_parameters:");
        gd.addNumericField("Sigma", sigma, 2);
        gd.addNumericField("Lower_Threshold", lowerThresh, 2);
        gd.addNumericField("Upper_Threshold", upperThresh, 2);
        gd.addCheckbox("Darkline", isDarkLine);
        gd.addCheckbox("Correct_position", doCorrectPosition);
        gd.addCheckbox("Estimate_width", doEstimateWidth);
        gd.addCheckbox("Extend_line", doExtendLine);
        gd.addCheckbox("Show_junction_points", showJunctionPoints);
        gd.addCheckbox("Show_IDs", showIDs);
        gd.addCheckbox("Verbose mode", verbose);
        gd.addCheckbox("DisplayResults", displayResults);
        gd.addCheckbox("Add_to_Manager", addToRoiManager);

        final String[] overlap = new String[OverlapOption.values().length];
        for (int i = 0; i < overlap.length; i++) {
            overlap[i] = OverlapOption.values()[i].name();
        }

        gd.addChoice("Method_for_overlap_resolution", overlap, overlapOption.name());

        gd.addHelp("http://fiji.sc/Ridge_Detection");
        gd.addDialogListener(this);
        gd.addPreviewCheckbox(pfr);
        gd.addButton("Reset settings to default", new ResetToDefaultListener(gd));
        gd.showDialog();
        if (gd.wasCanceled()) {
            imp.setOverlay(null);
            return DONE;
        } else {
            isPreview = false;
        }
        lineWidth = gd.getNextNumber();
        contrastHigh = gd.getNextNumber();
        contrastLow = gd.getNextNumber();
        sigma = gd.getNextNumber();
        lowerThresh = gd.getNextNumber();
        upperThresh = gd.getNextNumber();
        isDarkLine = gd.getNextBoolean();
        doCorrectPosition = gd.getNextBoolean();
        doEstimateWidth = gd.getNextBoolean();
        doExtendLine = gd.getNextBoolean();
        showJunctionPoints = gd.getNextBoolean();
        showIDs = gd.getNextBoolean();
        verbose = gd.getNextBoolean();
        displayResults = gd.getNextBoolean();
        addToRoiManager = gd.getNextBoolean();
        overlapOption = OverlapOption.valueOf(gd.getNextChoice());
        saveSettings();

        result = new ArrayList<Lines>();
        resultJunction = new ArrayList<Junctions>();

        int labels = IJ.setupDialog(imp, DOES_8G + FINAL_PROCESSING + PARALLELIZE_STACKS);
        doStack = (labels != DOES_8G + FINAL_PROCESSING + PARALLELIZE_STACKS);

        return labels;
    }

    private void readSettings() {
        lineWidth = Prefs.get("RidgeDetection.lineWidth", lineWidthDefault);
        contrastHigh = Prefs.get("RidgeDetection.contrastHigh", contrastHighDefault);
        contrastLow = Prefs.get("RidgeDetection.contrastLow", contrastLowDefault);
        sigma = Prefs.get("RidgeDetection.sigma", sigmaDefault);
        lowerThresh = Prefs.get("RidgeDetection.lowerThresh", lowerThreshDefault);
        upperThresh = Prefs.get("RidgeDetection.upperThresh", upperThreshDefault);
        isDarkLine = Prefs.get("RidgeDetection.isDarkLine", isDarkLineDefault);
        doCorrectPosition = Prefs.get("RidgeDetection.doCorrectPosition", doCorrectPositionDefault);
        doEstimateWidth = Prefs.get("RidgeDetection.doEstimateWidth", doEstimateWidthDefault);
        doExtendLine = Prefs.get("RidgeDetection.doExtendLine", doExtendLineDefault);
        showJunctionPoints = Prefs.get("RidgeDetection.showJunctionPoints", showJunctionPointsDefault);
        showIDs = Prefs.get("RidgeDetection.showIDs", showIDsDefault);
        verbose = Prefs.get("RidgeDetection.verbose", verboseDefault);
        displayResults = Prefs.get("RidgeDetection.displayResults", displayResultsDefault);
        addToRoiManager = Prefs.get("RidgeDetection.addToRoiManager", addToRoiManagerDefault);
        String overlapOptionString = Prefs.get("RidgeDetection.overlapOption", OverlapOption.NONE.name());
        overlapOption = OverlapOption.valueOf(overlapOptionString);
    }

    private void saveSettings() {
        Prefs.set("RidgeDetection.lineWidth", lineWidth);
        Prefs.set("RidgeDetection.contrastHigh", contrastHigh);
        Prefs.set("RidgeDetection.contrastLow", contrastLow);
        Prefs.set("RidgeDetection.sigma", sigma);
        Prefs.set("RidgeDetection.lowerThresh", lowerThresh);
        Prefs.set("RidgeDetection.upperThresh", upperThresh);
        Prefs.set("RidgeDetection.isDarkLine", isDarkLine);
        Prefs.set("RidgeDetection.doCorrectPosition", doCorrectPosition);
        Prefs.set("RidgeDetection.doEstimateWidth", doEstimateWidth);
        Prefs.set("RidgeDetection.doExtendLine", doExtendLine);
        Prefs.set("RidgeDetection.showJunctionPoints", showJunctionPoints);
        Prefs.set("RidgeDetection.showIDs", showIDs);
        Prefs.set("RidgeDetection.verbose", verbose);
        Prefs.set("RidgeDetection.displayResults", displayResults);
        Prefs.set("RidgeDetection.addToRoiManager", addToRoiManager);
        Prefs.set("RidgeDetection.overlapOption", overlapOption.name());
    }

    public void addToRoiManager() {
        RoiManager rm = RoiManager.getInstance();
        if (rm == null) {
            rm = new RoiManager();

        }
        for (Lines contours : result) {
            for (Line c : contours) {

                float[] x = c.getXCoordinates();
                for (int j = 0; j < x.length; j++) {
                    x[j] = (float) (x[j] + 0.5);
                }
                float[] y = c.getYCoordinates();
                for (int j = 0; j < y.length; j++) {
                    y[j] = (float) (y[j] + 0.5);
                }

                FloatPolygon p = new FloatPolygon(x, y, c.getNumber());
                Roi r = new PolygonRoi(p, Roi.FREELINE);
                r.setPosition(c.getFrame());
                r.setName("C" + c.getID());

                rm.addRoi(r);

            }
        }
        for (Junctions junctions : resultJunction) {
            for (Junction j : junctions) {

                PointRoi pr = new PointRoi(j.x + 0.5, j.y + 0.5);
                pr.setName("JP-C" + j.getLine1().getID() + "-C" + j.getLine2().getID());
                pr.setPosition(j.getLine1().getFrame());
                rm.addRoi(pr);
            }
        }

        rm.setVisible(true);
        rm.runCommand("UseNames", "true");

    }

    private void createResultsTable(boolean showJunctions) {
        ResultsTable rt = new ResultsTable();
        rt.setPrecision(3);
        for (Lines contours : result) {
            for (Line c : contours) {
                for (int i = 0; i < c.num; i++) {
                    rt.incrementCounter();
                    rt.addValue("Frame", IJ.d2s(contours.getFrame(), 0));
                    rt.addValue("Contour ID", IJ.d2s(c.getID(), 0));
                    rt.addValue("Pos.", IJ.d2s(i + 1, 0));
                    rt.addValue("X", IJ.d2s(c.col[i], 1));

                    rt.addValue("Y", IJ.d2s(c.row[i], 1));
                    rt.addValue("Length", IJ.d2s(c.estimateLength(), 1));
                    if (doCorrectPosition && doEstimateWidth) {
                        rt.addValue("Contrast", Math.abs(c.intensity[i]));
                        rt.addValue("Asymmetry", Math.abs(c.asymmetry[i]));
                    }
                    if (doEstimateWidth) {

                        rt.addValue("Line width", IJ.d2s(c.width_l[i] + c.width_r[i], 1));
                        rt.addValue("Angle of normal", IJ.d2s(c.angle[i], 2));
                    }
                    rt.addValue("Class", c.getContourClass().toString().substring(5));
                }
            }
        }

        rt.show("Contours");

        if (showJunctions) {
            ResultsTable rt2 = new ResultsTable();
            rt2.setPrecision(0);
            for (Junctions junctions : resultJunction) {
                for (Junction j : junctions) {
                    rt2.incrementCounter();
                    rt2.addValue("Frame", junctions.getFrame());
                    rt2.addValue("Contour ID 1", j.getLine1().getID());//c.get( j.cont1)
                    rt2.addValue("Contour ID 2", j.getLine2().getID());
                    rt2.addValue("X", IJ.d2s(j.x, 1));
                    rt2.addValue("Y", IJ.d2s(j.y, 1));
                }
            }
            rt2.show("Junctions");
        }
    }

    private void displayContours() {
        imp.setOverlay(null);
        Overlay ovpoly = new Overlay();

        double px, py, nx, ny, px_r = 0, py_r = 0, px_l = 0, py_l = 0;
        double last_w_r, last_w_l;

        // Print contour and boundary
        for (int k = 0; k < result.size(); k++) {
            for (int i = 0; i < result.get(k).size(); i++) {
                FloatPolygon polyMitte = new FloatPolygon();

                FloatPolygon polyR = new FloatPolygon();
                FloatPolygon polyL = new FloatPolygon();
                Line cont = result.get(k).get(i);
                int num_points = cont.num;
                last_w_r = 0;
                last_w_l = 0;

                for (int j = 0; j < num_points; j++) {

                    px = cont.col[j];
                    py = cont.row[j];
                    nx = Math.sin(cont.angle[j]);
                    ny = Math.cos(cont.angle[j]);
                    if (doEstimateWidth) {
                        px_r = px + cont.width_r[j] * nx;
                        py_r = py + cont.width_r[j] * ny;
                        px_l = px - cont.width_l[j] * nx;
                        py_l = py - cont.width_l[j] * ny;
                    }

                    polyMitte.addPoint((px + 0.5), (py + 0.5));
                    if (doEstimateWidth) {
                        if (last_w_r > 0 && cont.width_r[j] > 0) {
                            polyR.addPoint((px_r + 0.5), (py_r + 0.5));
                        }
                        if (last_w_l > 0 && cont.width_l[j] > 0) {
                            polyL.addPoint((px_l + 0.5), (py_l + 0.5));
                        }
                    }
                    if (doEstimateWidth) {
                        last_w_r = cont.width_r[j];
                        last_w_l = cont.width_l[j];
                    }
                }

                PolygonRoi polyRoiMitte = new PolygonRoi(polyMitte, Roi.POLYLINE);

                polyRoiMitte.setStrokeColor(Color.red);
                int position = result.get(k).getFrame();
                if (!doStack || isPreview) {
                    position = imp.getCurrentSlice();
                }

                polyRoiMitte.setPosition(position);
                ovpoly.add(polyRoiMitte);

                if (doEstimateWidth) {
                    if (polyL.npoints > 1) {
                        PolygonRoi polyRoiRand1 = new PolygonRoi(polyL, Roi.POLYLINE);
                        polyRoiRand1.setStrokeColor(Color.green);
                        position = result.get(k).getFrame();
                        if (!doStack || isPreview) {
                            position = imp.getCurrentSlice();
                        }
                        polyRoiRand1.setPosition(position);
                        ovpoly.add(polyRoiRand1);

                        PolygonRoi polyRoiRand2 = new PolygonRoi(polyR, Roi.POLYLINE);
                        polyRoiRand2.setStrokeColor(Color.green);
                        polyRoiRand2.setPosition(position);
                        ovpoly.add(polyRoiRand2);
                    }
                }

                //Show IDs
                if (showIDs) {/*
                              int posx =  polyMitte.xpoints[0];
                              int posy =  polyMitte.ypoints[0];
                              if(cont.cont_class == contour_class.cont_start_junc){
                              posx =  polyMitte.xpoints[polyMitte.npoints-1];
                              posy =  polyMitte.ypoints[polyMitte.npoints-1];
                              }
                              */

                    int posx = (int) polyMitte.xpoints[polyMitte.npoints / 2];
                    int posy = (int) polyMitte.ypoints[polyMitte.npoints / 2];
                    TextRoi tr = new TextRoi(posx, posy, "" + cont.getID());
                    tr.setCurrentFont(new Font(Font.SANS_SERIF, Font.PLAIN, 9));
                    tr.setIgnoreClipRect(true);
                    tr.setStrokeColor(Color.orange);
                    tr.setPosition(resultJunction.get(k).getFrame());
                    ovpoly.add(tr);
                }
            }
        }
        if (showJunctionPoints) {
            // Print junctions

            for (int k = 0; k < resultJunction.size(); k++) {
                FloatPolygon pointpoly = new FloatPolygon();
                for (int i = 0; i < resultJunction.get(k).size(); i++) {

                    pointpoly.addPoint(resultJunction.get(k).get(i).x + 0.5, resultJunction.get(k).get(i).y + 0.5);
                }

                PointRoi pointroi = new PointRoi(pointpoly);
                pointroi.setShowLabels(false);
                int position = resultJunction.get(k).getFrame();
                if (!doStack || isPreview) {
                    position = imp.getCurrentSlice();
                }
                pointroi.setPosition(position);
                ovpoly.add(pointroi);
            }
        }
        if (ovpoly.size() > 0) {
            imp.setOverlay(ovpoly);
        }
    }

    @Override
    public void setNPasses(int nPasses) {
        IJ.showProgress(nPasses, imp.getNSlices());
    }

    @Override
    public boolean dialogItemChanged(GenericDialog gd, AWTEvent e) {
        imp.setOverlay(null);
        boolean lwChanged = false;
        boolean contHighChanged = false;
        boolean contLowChanged = false;
        boolean darklineChanged = false;

        double lwCand = gd.getNextNumber();
        double diff = Math.abs(lwCand - lineWidth);
        if (diff > 0.0001) {
            lineWidth = lwCand;
            lwChanged = true;
        }
        double conCand = gd.getNextNumber();
        diff = Math.abs(conCand - contrastHigh);
        if (diff > 0.0001) {
            contrastHigh = conCand;
            contHighChanged = true;
        }

        conCand = gd.getNextNumber();
        diff = Math.abs(conCand - contrastLow);
        if (diff > 0.0001) {
            contrastLow = conCand;
            contLowChanged = true;
        }

        boolean darklineCand = gd.getNextBoolean();
        if (darklineCand != isDarkLine) {
            isDarkLine = darklineCand;
            darklineChanged = true;
        }

        doCorrectPosition = gd.getNextBoolean();
        doEstimateWidth = gd.getNextBoolean();
        doExtendLine = gd.getNextBoolean();
        showJunctionPoints = gd.getNextBoolean();
        showIDs = gd.getNextBoolean();
        verbose = gd.getNextBoolean();
        displayResults = gd.getNextBoolean();
        addToRoiManager = gd.getNextBoolean();
        overlapOption = OverlapOption.valueOf(gd.getNextChoice());
        if (lwChanged || contHighChanged || contLowChanged) {
            contrastOrLineWidthChangedOnce = true;
        }

        if (lwChanged || contHighChanged || contLowChanged || (darklineChanged && contrastOrLineWidthChangedOnce)) {
            double estimatedSigma = lineWidth / (2 * Math.sqrt(3)) + 0.5;
            TextField textSigma = (TextField) gd.getNumericFields().get(3);
            textSigma.setText("" + IJ.d2s(estimatedSigma, 2));
            textSigma.setEditable(true);
            double clow = contrastLow;
            if (isDarkLine) {
                clow = 255 - contrastHigh;
            }
            double estimatedLowerThresh = Math.floor(Math.abs(-2 * clow * (lineWidth / 2.0)
                    / (Math.sqrt(2 * Math.PI) * estimatedSigma * estimatedSigma * estimatedSigma)
                    * Math.exp(-((lineWidth / 2.0) * (lineWidth / 2.0)) / (2 * estimatedSigma * estimatedSigma))));
            TextField textLowThresh = (TextField) gd.getNumericFields().get(4);
            textLowThresh.setText("" + IJ.d2s(estimatedLowerThresh * 0.17, 2));
            textLowThresh.setEditable(true);
            double chigh = contrastHigh;
            if (isDarkLine) {
                chigh = 255 - contrastLow;
            }
            double estimatedUpperThresh = Math.floor(Math.abs(-2 * chigh * (lineWidth / 2.0)
                    / (Math.sqrt(2 * Math.PI) * estimatedSigma * estimatedSigma * estimatedSigma)
                    * Math.exp(-((lineWidth / 2.0) * (lineWidth / 2.0)) / (2 * estimatedSigma * estimatedSigma))));
            TextField textUppThresh = (TextField) gd.getNumericFields().get(5);
            textUppThresh.setText("" + IJ.d2s(estimatedUpperThresh * 0.17, 2));
            textUppThresh.setEditable(true);
        }
        sigma = gd.getNextNumber();
        lowerThresh = gd.getNextNumber();
        upperThresh = gd.getNextNumber();
        if (lowerThresh >= upperThresh || sigma < 0.4 || Double.isNaN(sigma + lowerThresh + upperThresh)) {
            return false;
        }

        isPreview = gd.isPreviewActive();

        return true;
    }

    @Override
    public void run(ImageProcessor ip) {

        if (isPreview) {
            Line.resetCounter();
            result = new ArrayList<Lines>();
            resultJunction = new ArrayList<Junctions>();

        }

        LineDetector detect = new LineDetector();
        detect.bechatty = verbose;

        result.add(detect.detectLines(ip, sigma, upperThresh, lowerThresh, isDarkLine, doCorrectPosition,
                doEstimateWidth, doExtendLine, overlapOption));
        usedOptions = detect.getUsedParamters();
        resultJunction.add(detect.getJunctions());

        if (isPreview) {
            displayContours();
            Line.resetCounter();
            result = new ArrayList<Lines>();
            resultJunction = new ArrayList<Junctions>();
        }
    }

    /**
     * Return the detected lines
     * @return ArrayList of lines for each frame.
     */
    public ArrayList<Lines> getDetectedLines() {
        return result;
    }

    /**
     * Return the detected junctions
     * @return ArrayList of junctions for each frame.
     */
    public ArrayList<Junctions> getDetectedJunctions() {
        return resultJunction;
    }

    /**
     * Returns the parameter which were used for the analysis
     * @return The used parameters
     */
    public Options getParameters() {
        return usedOptions;
    }

}