package vnt;
/**
 * Basic_Segment.java - v1.0
 * Began: June 27, 2005
 * Last Updated: July 28, 2005 
 * 
 * Copyright (C) 2005 - Michael D. Miller - mdm162@truman.edu
 * Truman State University
 * 100 E. Normal
 * Kirksville, MO - 63501
 * 
 * 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 */

import ij.*;
import ij.gui.*;
import java.awt.*;
import java.awt.Color.*;
import ij.plugin.filter.PlugInFilter;
import ij.process.*;
import java.io.*;
import ij.io.*;
import java.lang.String.*;
import java.lang.Math.*;
import ij.plugin.filter.*;

import Coordinate.*;
import java.util.Stack;

/**
 * <p>Plug-in for ImageJ that attempts to automatically segment
 * vascular cells from the background. Performs a very specific
 * segmentation designed for vascular cells grown on a Matrigel 
 * assay.</p>
 * 
 * <p>The method for segmentation is as described in the paper:</p>
 * <ul>
 * <li>Article{niemisto2005,</li>
 * <li>  author = "A. Niemisto",</li>
 * <li>  title = "Robust Quantification of In Vitro Angiogenesis Through Image Analysis",</li>
 * <li>  journal = "{IEEE} Trans. on Medical Imaging",</li>
 * <li>  year = "2005",</li>
 * <li>  volume = "24",</li>
 * <li>  pages = "549--553",</li>
 * <li>  month = apr,</li>
 * <li>  keywords = "Angiogenesis, image analysis, quantification, segmentation",</li>
 * <li>  note = "segmentation method" }</li>
 * </ul>
 * 
 * <p>The low threshold is selected by the Otsu method:</p>
 * <ul>
 * <li>Article{otsu79,</li>
 * <li>  author = "N. Otsu",</li>
 * <li>  title = "A threshold selection method from gray level histograms",</li>
 * <li>  journal = "{IEEE} Trans. Systems, Man and Cybernetics",</li>
 * <li>  year = "1979",</li>
 * <li>  volume = "9",</li>
 * <li>  pages = "62--66",</li>
 * <li>  month = mar,</li>
 * <li>  keywords = "threshold selection",</li>
 * <li>  note = "minimize inter class variance" }</li>
 * </ul> 
 * 
 * <p>The high threshold pixels are selected by subtracting 90 from the 
 * light-corrected plane at 127, multiplying the result by 4 (leaving the
 * estimated mean at 151. The threshold is set to the mean - std.dev,
 * where the std.dev. is expected to be approximately 30.</p>
 * 
 * <p>Note: At this point in time, the plugin DOES NOT
 *    use a rule set to decide if a 4-connected component
 *    should be added or not.
 * It simply adds blindly. This may need to be corrected depending
 * on the quality of the result in practice.</p> 
 * 
 * @author Michael Miller - Truman State University
 * @version 1.0
 * @since 1.0
 */
public class Basic_Segment extends VascularNetworkToolkit implements PlugInFilter {

    /**
    * Specifies the preconditions for the plug-in.
    * If this method succeeds then run() is called.
    *
    * <p>Pre: ImageJ is running and an 8-bit grayscale image is open. The plug-in was just activated.
    * <br />Post: Either an argument was processed, the image was not saved to a local folder, or the plug-in is cleared to run on the image.
    * @param arg Required by the interface. The argument list passed to the plug-in.
    * @param imp Required by the interface. The ImagePlus datastructure holding (among other things) information to grab path and filename.
    * @return If DONE is returned, ImageJ quits without run()'ing the plug-in. Otherwise, the plug-in signals to ImageJ that this plugin only handles 8-bit (256 grayscale) and will not change the original image.
    * @see #run
    */
    public int setup(String arg, ImagePlus ip) {
        if (arg.equals("about")) { 
            showAbout("Basic Segment","  * Calculates two thresholds, excessive and conservative, and finds the connected components by 'growing' the low threshold into the high threshold. (Copyright 2005. Michael Miller mdm162@truman.edu)");
            return DONE; // exit without run()
        }

        // will only save if this succeeds
        getFileInformation(ip);
                            
        return DOES_8G+NO_CHANGES; // success, run()
    }

    /**
     * Performs specific segmentation designed for vascular cells 
     * grown on a Matrigel assay.
     * 1) Gets a low and high estimate for thresholding.
     * 2) Fills in the conservative estimate.
     * 3) Uses the conservative estimate to find ("grow") the large aggressive estimate pieces.
     *  
     * <p>Pre: The image was cleared to run by the setup() method.
     * <br />Post: The image is processed by the segmentation routines. A new segmented binary image is drawn. 
     * @param bp Required by the interface. The access information to the original image.
     * @see #setup
     * @see #getLowThreshold 
     * @see #getHighThreshold 
     * @see #generateSegmented 
     */
    public void run(ImageProcessor bp){ 
        // get the width and height information
        getDimensions(bp);
        //get low threshold
        ImageProcessor lowThreshold = getLowThreshold(bp); 
        // get high threshold
        ImageProcessor highThreshold = getHighThreshold(bp);
        // generate Segmented image
        generateSegmented(lowThreshold, highThreshold);
        // save the segmented image
        saveFile("segmented");
        return;     
    }
    
    /**
     * Calculates and returns a low (underestimate) threshold.
     * 
     * Note: At this time the method fills in a new image.
     * This is for display (and error checking) purposes only
     * and will not be useful in the final release.
     * 
     * This method was created entirely with code lifted
     * from OtsuThresholding_.java.
     * Authors: Christopher Mei (christopher.mei at sophia.inria.fr)
     *                 Anthony Joshua (Anthony.Joshua at utoronto.ca)
     *                 Tony Collins (tonyc at uhnresearch.ca) 
     * http://rsb.info.nih.gov/ij/plugins/otsu-thresholding.html
     * 
     * <p>Pre: The dimensions width and height must have been loaded prior to calling this method. The given ImageProcessor must be for the original image and must be valid.
     * <br />Post: A new image is created, the Otsu threshold of the original.
     * @param ip The Image Processor is required for the original image data and Histogram information.
     * @return If there was a problem (such as the given ImageProcessor was invalid), then the method returns null. Otherwise, the ImageProcessor for the low threshold (computed here by the Otsu Threshold method)
     * @see #generateSegmented 
     */
    public ImageProcessor getLowThreshold(ImageProcessor ip) {
        if ((ip == null) || (width < 1) || (height < 1)) {
            return null;
        }
        
        String title = "LowThreshold"; 
        // load the original image into memory
        pixel = VascularNetworkToolkit.LoadImage(ip);
        ImagePlus lowImp = NewImage.createByteImage (title, width, height, 1, NewImage.FILL_WHITE);
        ImageProcessor lownip = lowImp.getProcessor();

        GrayLevelClass.N = width*height;
        GrayLevelClass.probabilityHistogramDone = false;
        GrayLevelClass C1 = new GrayLevelClass((ByteProcessor) ip, true);
        GrayLevelClass C2 = new GrayLevelClass((ByteProcessor) ip, false);

        //IJ.showStatus("Iterating...");
    
        float fullMu = C1.getOmega()*C1.getMu()+C2.getOmega()*C2.getMu();
        //IJ.write("Full Omega : "+fullMu);
        double sigmaMax = 0;
        int threshold = 0, i, x, y;

        /** Start  **/
        for(i=0 ; i<255 ; i++) {
            //IJ.showStatus("Iteration "+i+"/"+256+"...");
            //IJ.showProgress(i/256);

            double sigma = C1.getOmega()*(Math.pow(C1.getMu()-fullMu,2))+C2.getOmega()*(Math.pow(C2.getMu()-fullMu,2));
        
            if(sigma>sigmaMax) {
                sigmaMax = sigma;
                threshold = C1.getThreshold();
            }

            C1.addToEnd();
            C2.removeFromBeginning();
        }

        for(y=0; y<height; y++) {
            for(x=0; x<width; x++) {
                if (pixel[x][y] <= threshold) {
                    lownip.putPixel(x,y,BLACK);
                } 
            }
        }
        
        lowImp.show();
        IJ.selectWindow(title);
        return lownip;
    }

    /**
     * Returns the high (overestimate) threshold.
     * This method operates on the basis that the lighting correction
     * algorithm leveled the background to an estimated average of 127.
     * As a result, a simple way to enhance the contrast is to:
     * 1) Subtract 90 from the image. (Vessels are now 0-10.. the hardest to see are 20~30)
     * 2) Multiply by 4 (The estimated average is now 148~152, vessels are generally between 0-80).
     * 3) Threshold by the mean - std.dev. (std.dev. is expected to be about 30).
     * This is an aggressive threshold that picks up a lot of noise, but it 
     * also successfully captures the majority of vessels. 
     * 
     * <p>Pre: The dimensions width and height must have been loaded prior to calling this method. The given ImageProcessor must be for the original image and must be valid.
     * <br />Post: A new image is created containing the high threshold pixels on success. On failure, nothing is created and the method returns null.
     * @param ip The Image Processor is required for the original image data and Histogram information.
     * @return The ImageProcessor for the newly created image representing the high threshold. 
     * @see #generateSegmented 
     */    
    public ImageProcessor getHighThreshold(ImageProcessor ip) {
        if ((ip == null) || (width < 1) || (height < 1)) {
            return null;
        }
        
        String title = "HighThreshold";
        // load the original image into memory
        pixel = VascularNetworkToolkit.LoadImage(ip);
        ImagePlus highImp = NewImage.createByteImage (title, width, height, 1, NewImage.FILL_WHITE);
        ImageProcessor highnip = highImp.getProcessor();
        // create a copy of the original image and store it in the new image
        int threshold,x,y;
        for(y=0; y<height; y++) {
            for(x=0; x<width; x++) {
                highnip.putPixel(x,y,pixel[x][y]);
            }
        }
        
        // subtract 90 from the light corrected
        highnip.add(-90);
        // multiply by 4
        highnip.multiply(4);
        
        // get stats on the new image
        ImageStatistics stats=highImp.getStatistics();
        threshold = (int)(stats.mean - stats.stdDev);
        
        // threshold the image
        highnip.threshold(threshold);
        // display and set active
        highImp.show();
        IJ.selectWindow(title);
        
        return highnip;
    }
    
    /**
     * "Grows" the low threshold into the high threshold.
     * Searches iteratively for the 4-connected components.
     * All points in the low (conservative) threshold are added to the
     * search stack. Any pixels from the search stack which are
     * 4-connected to pixels on the high threshold are iteratively added.
     * Any pixel that is processed on the search stack is added to the final
     * segmented image.
     *  
     * <p>Pre: The low threshold and high threshold must have been computed.
     * <br />Post: On success, a new image with the segmented information is created and displayed. Also, the local member pixel is filled with non-BLACK values. On failure (if a given ImageProcessor is null), no changes are made, and false is returned. 
     * @param low The ImageProcessor for the low threshold.
     * @param high The ImageProcessor for the high threshold.
     * @return Returns true on success, false otherwise.
     * @see #getLowThreshold 
     * @see #getHighThreshold 
     */
    public boolean generateSegmented(ImageProcessor low, ImageProcessor high) {
        if ((low == null) || (high == null)) {
            return false;
        }
        Stack searchStack = new Stack();
        Coordinate myPixel;
        int x=0, y=0, color=0, i=0;
        // build our search stack from the low threshold
        for(y=0; y<height; y++) {
            for(x=0; x<width; x++) {
                if (low.getPixel(x,y) == BLACK) {
                    searchStack.push(new Coordinate(x,y,pixel[x][y]));
                } 
            }
        }
        String title = "Segmented";
        // load a temporary copy for our search to the high threshold image
        pixel = VascularNetworkToolkit.LoadImage(high);
        // perform the segmentation and generate a binary image
        ImagePlus imp = NewImage.createByteImage (title, width, height, 1, NewImage.FILL_WHITE);
        ImageProcessor nip = imp.getProcessor();
        
        if (animatedDisplay) {
            imp.show();
            IJ.selectWindow(title);
        }
        
        while (!searchStack.isEmpty()) {
            myPixel = (Coordinate)searchStack.pop();
            x = myPixel.getX();
            y = myPixel.getY();
            color = myPixel.getColor();
            // make sure we didn't already place this pixel
            if (pixel[x][y] == BLACK) {
                pixel[x][y] = WHITE;
                nip.putPixel(x,y,BLACK);
                if (animatedDisplay)
                    imp.updateAndDraw();
                // check the cardinal directions to add 4-connected
                // neighbors that satisfy the high threshold
                for (x=myPixel.getX()-1; x<=myPixel.getX()+1;x++) {
                    for (y=myPixel.getY()-1; y<=myPixel.getY()+1;y++) {
                        color = getColor(x,y);
                        if (color == BLACK) {
                            searchStack.push(new Coordinate(x, y, color));
                        }
                    }
                }
            }
        }
        // finished our segmented image - display it
        if (!animatedDisplay) {
            imp.show();
            IJ.selectWindow(title);
        }
        return true; 
    }
}