package vnt;
/**
 * Eye_Candy.java - v1.0
 * Began: July 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.*;

/**
 * <p>A simple class that opens the original color image and overlays
 * graphics generated by the Vascular Network Toolkit.</p>
 * 
 * @author Michael Miller - Truman State University
 * @version 1.0
 * @since 1.0
 */
public class Eye_Candy 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("Eye Candy","  * Generates a variety of visualizations overlayed on the original image. Used for image processing qualitative comparison and evaluation. (Copyright 2005. Michael Miller mdm162@truman.edu)");
            return DONE; // exit without run()
        }

        boolean processImage = getFileInformation(ip);
        if (!processImage) {
            IJ.error("Must have a file path. Please save this image to a local folder.");
            return DONE; // exit without run()
        }
                    
        return DOES_ALL+NO_CHANGES; // success, run()
    }

    /**
     * Receives a binary image (specifically a thinned, pruned distance map
     * skeletonization) and computes information regarding the topological
     * properties of the image.
     * 1) Parses the image for possible node locations.
     * 2) Accesses the distance map to calculate node radii information.
     * 3) Simplifies the structures by testing for node overlap.
     * 4) Performs graph theoretic analysis based on the information generated. 
     *  
     * <p>Pre: The image was cleared to run by the setup() method.
     * <br />Post: Quantitative data is finally extracted from the image. 
     * @param bp Required by the interface. The access information to the original image.
     * @see #setup
     * @see #RED
     * @see #YELLOW
     * @see #GREEN
     * @see #drawOutlineOverlay
     * @see #drawNodeOverlay
     * @see #drawSkeletonOverlay
     * @see #drawPrunedSkeletonOverlay
     * @see #drawBlendedSegmentationOverlay
     */
    public void run(ImageProcessor bp){ 
        // get the width and height information
        getDimensions(bp);
        // load the original image
        if (!loadOriginalImage()) {
            // could not load original, quit
            return;
        }
        int totalStacks = 9;
        if (generateEyeCandyWithOriginalImage)
            totalStacks ++;
        if (generateLightingCorrectionSurfaceEyeCandy)
            totalStacks ++;
        // create a stack image for multiple overlays
        String title = "Eye Candy";
        ImagePlus imp = NewImage.createRGBImage (title, width, height, totalStacks, NewImage.FILL_WHITE);
        ImageProcessor nip;
        int stackIndex = 1;
        // draw original image
        if (generateEyeCandyWithOriginalImage) {
            imp.setSlice(stackIndex++);
            nip = imp.getProcessor();
            drawOriginalImage(nip);
        }
        // draw light correction surface
        if (generateLightingCorrectionSurfaceEyeCandy) {
            imp.setSlice(stackIndex++);
            nip = imp.getProcessor();
            drawOriginalImage(nip);         
        }
        // display blended segmented overlay
        imp.setSlice(stackIndex++);
        nip = imp.getProcessor();
        drawBlendedSegmentationOverlay(nip, RED);
        // display segmented overlay
        imp.setSlice(stackIndex++);
        nip = imp.getProcessor();
        drawSegmentedOverlay(nip, RED);
        // display skeleton overlay
        imp.setSlice(stackIndex++);
        nip = imp.getProcessor();
        drawSkeletonOverlay(nip, GREEN);
        // display pruned skeleton overlay
        imp.setSlice(stackIndex++);
        nip = imp.getProcessor();
        drawPrunedSkeletonOverlay(nip, GREEN);
        // display pruned skeleton's outline overlay
        imp.setSlice(stackIndex++);
        nip = imp.getProcessor();
        drawOutlineOverlay(nip, RED);
        // display pruned's outline and skeleton
        imp.setSlice(stackIndex++);
        nip = imp.getProcessor();
        drawOutlineSkeletonOverlay(nip, RED, GREEN);
        // display node-skeleton overlay
        imp.setSlice(stackIndex++);
        nip = imp.getProcessor();
        drawSkeletonNodeOverlay(nip, GREEN, YELLOW);
        // display node graph overlay
        imp.setSlice(stackIndex++);
        nip = imp.getProcessor();
        drawGraphOverlay(nip, YELLOW);
        // display merged node graph overlay
        imp.setSlice(stackIndex++);
        nip = imp.getProcessor();
        drawMergedGraphOverlay(nip, YELLOW);
        // all done, set to first slice and display
        imp.setSlice(1);
        imp.show();
        IJ.selectWindow(title);
        // save the visualizations
        saveFile("eyecandy");
        return; 
    }
    
    /**
     * Opens the original image and loads it into memory for access
     * by all the display methods in this class.
     *  
     * <p>Pre: This class was setup appropriately with an image from the processed set of images. Ie: File information exists.
     * <br />Post: The original image will be loaded into local memory.
     * @return Returns true on successful load. False otherwise.
     */
    private boolean loadOriginalImage() {
        int x=0, y=0;
        
        // load the original image
        IJ.open(directory+originalName);
        ImagePlus originalImage = IJ.getImage();
        ImageProcessor originalIP = originalImage.getProcessor();
        originalImage.show();
        IJ.selectWindow(originalName);
        IJ.run("RGB Color"); // required if final is also RGB (makes things blue if it's only 8-bit)
        pixel = VascularNetworkToolkit.LoadImage(originalIP);
        // close the original after loading it's pixels into memory     
        IJ.run("Close");
        // something bad happened, quit with error
        if (pixel == null) {
            return false;
        }
        return true;
    }
    
    /**
     * Draws the original image into the given ImageProcessor.
     * 
     * <p>Pre: This class was setup appropriately with an image from the processed set of images.
     * <br />Post: A new stack should be created of the original color image.
     * @param nip The ImageProcessor that will be drawn on.
     * @return Returns true on successful draw. False otherwise.
     */
    public boolean drawOriginalImage(ImageProcessor nip) {
        int x=0, y=0;

        // draw the original image
        for(y=0; y<height; y++) {
            for(x=0; x<width; x++) {
                nip.putPixel(x,y,getColor(x,y));
            }
        }
        
        return true;
    }

    /**
     * Draws the lighting correction surface into the given ImageProcessor.
     * 
     * <p>Pre: This class was setup appropriately with an image from the processed set of images.
     * <br />Post: A new stack should be created of the original color image.
     * @param nip The ImageProcessor that will be drawn on.
     * @return Returns true on successful draw. False otherwise.
     */
    public boolean drawLightingCorrectionSurface(ImageProcessor nip) {
        int x=0, y=0;

        // load the outline
        IJ.open(directory+"surface_"+originalName);
        ImagePlus surfaceImage = IJ.getImage();
        ImageProcessor surfaceIP = surfaceImage.getProcessor();

        // draw the surface in the appropriate color
        for(y=0; y<height; y++) {
            for(x=0; x<width; x++) {
                nip.putPixel(x,y,surfaceIP.getPixel(x,y));
            }
        }
        
        IJ.run("Close");        
        return true;
    }

    /**
     * Opens the EDM outline, and generates a new image with the
     * EDM generated outline overlayed in a particular color over the
     * original.
     *  
     * <p>Pre: This class was setup appropriately with an image from the processed set of images.
     * <br />Post: A new image should be created the original color image with a graphical overlay.
     * @param color The 32-bit color to draw the overlay. 0xAABBCCDD where AA is the alpha, BB is the red channel, CC is the green channel, and DD is the green channel. 
     * @return Returns true on successful draw. False otherwise.
     */
    public boolean drawOutlineOverlay(ImageProcessor nip, int color) {
        int x=0, y=0;
        
        // load the outline
        IJ.open(directory+"outline_"+originalName);
        ImagePlus outlineImage = IJ.getImage();
        ImageProcessor outlineIP = outlineImage.getProcessor();

        // draw the original image
        for(y=0; y<height; y++) {
            for(x=0; x<width; x++) {
                nip.putPixel(x,y,getColor(x,y));
            }
        }
        // draw the outline in the given color
        for(y=0; y<height; y++) {
            for(x=0; x<width; x++) {
                if (outlineIP.getPixel(x,y) != WHITE) {
                    nip.putPixel(x,y,color);
                }
            }
        }
        
        IJ.run("Close");
        return true;
    }

    /**
     * Opens the node and skeleton overlays, and generates a new image with the
     * nodes and skeleton overlayed in a particular color over the original.
     *  
     * <p>Pre: This class was setup appropriately with an image from the processed set of images.
     * <br />Post: A new image should be created the original color image with a graphical overlay.
     * @param skeletonColor The 32-bit color to draw the skeleton overlay. 0xAABBCCDD where AA is the alpha, BB is the red channel, CC is the green channel, and DD is the green channel. 
     * @param nodeColor The 32-bit color to draw the node overlay. 0xAABBCCDD where AA is the alpha, BB is the red channel, CC is the green channel, and DD is the green channel. 
     * @return Returns true on successful draw. False otherwise.
     */
    public boolean drawSkeletonNodeOverlay(ImageProcessor nip, int skeletonColor, int nodeColor) {
        int x=0, y=0;
        
        // load the node overlay
        IJ.open(directory+"nodes_"+originalName);
        ImagePlus nodesImage = IJ.getImage();
        ImageProcessor nodesIP = nodesImage.getProcessor();
        
        // load the pruned skeleton
        IJ.open(directory+"pruned_"+originalName);
        ImagePlus prunedImage = IJ.getImage();
        ImageProcessor prunedIP = prunedImage.getProcessor();
        
        // draw the original image
        for(y=0; y<height; y++) {
            for(x=0; x<width; x++) {
                nip.putPixel(x,y,getColor(x,y));
            }
        }
        // draw the nodes in the given color
        for(y=0; y<height; y++) {
            for(x=0; x<width; x++) {
                if (nodesIP.getPixel(x,y) != WHITE) {
                    nip.putPixel(x,y,nodeColor);
                }
            }
        }

        // draw the pruned skeleton in the given color
        for(y=0; y<height; y++) {
            for(x=0; x<width; x++) {
                if (prunedIP.getPixel(x,y) != WHITE) {
                    nip.putPixel(x,y,skeletonColor);
                }
            }
        }
        
        IJ.run("Close");
        IJ.run("Close");
        return true;
    }

    /**
     * Opens the node overlay, and generates a new image with the
     * nodes overlayed in a particular color over the original.
     *  
     * <p>Pre: This class was setup appropriately with an image from the processed set of images.
     * <br />Post: A new image should be created the original color image with a graphical overlay.
     * @param color The 32-bit color to draw the overlay. 0xAABBCCDD where AA is the alpha, BB is the red channel, CC is the green channel, and DD is the green channel. 
     * @return Returns true on successful draw. False otherwise.
     */
    public boolean drawNodeOverlay(ImageProcessor nip, int color) {
        int x=0, y=0;
        
        // load the node overlay
        IJ.open(directory+"nodes_"+originalName);
        ImagePlus nodesImage = IJ.getImage();
        ImageProcessor nodesIP = nodesImage.getProcessor();
        
        // draw the original image
        for(y=0; y<height; y++) {
            for(x=0; x<width; x++) {
                nip.putPixel(x,y,getColor(x,y));
            }
        }
        // draw the nodes in the given color
        for(y=0; y<height; y++) {
            for(x=0; x<width; x++) {
                if (nodesIP.getPixel(x,y) != WHITE) {
                    nip.putPixel(x,y,color);
                }
            }
        }
        
        IJ.run("Close");
        return true;
    }
    
    /**
     * Opens the simplified graph representation, and generates a
     * new image with the graph overlayed in a particular color over the original.
     *  
     * <p>Pre: This class was setup appropriately with an image from the processed set of images.
     * <br />Post: A new image should be created the original color image with a graphical overlay.
     * @param color The 32-bit color to draw the overlay. 0xAABBCCDD where AA is the alpha, BB is the red channel, CC is the green channel, and DD is the green channel. 
     * @return Returns true on successful draw. False otherwise.
     */
    public boolean drawGraphOverlay(ImageProcessor nip, int color) {
        int x=0, y=0;
        
        // load the graph overlay
        IJ.open(directory+"graph_"+originalName);
        ImagePlus graphImage = IJ.getImage();
        ImageProcessor graphIP = graphImage.getProcessor();
        
        // draw the original image
        for(y=0; y<height; y++) {
            for(x=0; x<width; x++) {
                nip.putPixel(x,y,getColor(x,y));
            }
        }
        // draw the graph in the given color
        for(y=0; y<height; y++) {
            for(x=0; x<width; x++) {
                if (graphIP.getPixel(x,y) != WHITE) {
                    nip.putPixel(x,y,color);
                }
            }
        }
        
        IJ.run("Close");
        return true;
    }

    /**
     * Opens the merged graph representation, and generates a
     * new image with the graph overlayed in a particular color over the original.
     *  
     * <p>Pre: This class was setup appropriately with an image from the processed set of images.
     * <br />Post: A new image should be created the original color image with a graphical overlay.
     * @param color The 32-bit color to draw the overlay. 0xAABBCCDD where AA is the alpha, BB is the red channel, CC is the green channel, and DD is the green channel. 
     * @return Returns true on successful draw. False otherwise.
     */
    public boolean drawMergedGraphOverlay(ImageProcessor nip, int color) {
        int x=0, y=0;
        
        // load the graph overlay
        IJ.open(directory+"merged_"+originalName);
        ImagePlus graphImage = IJ.getImage();
        ImageProcessor graphIP = graphImage.getProcessor();
        
        // draw the original image
        for(y=0; y<height; y++) {
            for(x=0; x<width; x++) {
                nip.putPixel(x,y,getColor(x,y));
            }
        }
        // draw the graph in the given color
        for(y=0; y<height; y++) {
            for(x=0; x<width; x++) {
                if (graphIP.getPixel(x,y) != WHITE) {
                    nip.putPixel(x,y,color);
                }
            }
        }
        
        IJ.run("Close");
        return true;
    }
    
    /**
     * Opens the EDM skeleton, and generates a new image with the
     * skeleton overlayed in a particular color over the original.
     *  
     * <p>Pre: This class was setup appropriately with an image from the processed set of images.
     * <br />Post: A new image should be created the original color image with a graphical overlay.
     * @param color The 32-bit color to draw the overlay. 0xAABBCCDD where AA is the alpha, BB is the red channel, CC is the green channel, and DD is the green channel. 
     * @return Returns true on successful draw. False otherwise.
     */
    public boolean drawSkeletonOverlay(ImageProcessor nip, int color) {
        int x=0, y=0;
        
        // load the skeleton
        IJ.open(directory+"skeleton_"+originalName);
        ImagePlus skeletonImage = IJ.getImage();
        ImageProcessor skeletonIP = skeletonImage.getProcessor();

        // draw the original image
        for(y=0; y<height; y++) {
            for(x=0; x<width; x++) {
                nip.putPixel(x,y,getColor(x,y));
            }
        }
        // draw the skeleton in the given color
        for(y=0; y<height; y++) {
            for(x=0; x<width; x++) {
                if (skeletonIP.getPixel(x,y) != WHITE) {
                    nip.putPixel(x,y,color);
                }
            }
        }
        
        IJ.run("Close");
        return true;
    }
    
    /**
     * Opens the segmentation, and generates a new image with the 
     * segmented drawn solid overlayed in a particular color over the 
     * original.
     *  
     * <p>Pre: This class was setup appropriately with an image from the processed set of images.
     * <br />Post: A new image should be created the original color image with a graphical overlay.
     * @param color The 32-bit color to draw the overlay. 0xAABBCCDD where AA is the alpha, BB is the red channel, CC is the green channel, and DD is the green channel. 
     * @return Returns true on successful draw. False otherwise.
     */
    public boolean drawSegmentedOverlay(ImageProcessor nip, int color) {
        int x=0, y=0;
        
        // load the segmentation
        IJ.open(directory+"segmented_"+originalName);
        ImagePlus segmentedImage = IJ.getImage();
        ImageProcessor segmentedIP = segmentedImage.getProcessor();

        // draw the original image
        for(y=0; y<height; y++) {
            for(x=0; x<width; x++) {
                nip.putPixel(x,y,getColor(x,y));
            }
        }
        // draw the segmented overlay in the given color
        for(y=0; y<height; y++) {
            for(x=0; x<width; x++) {
                if (segmentedIP.getPixel(x,y) != WHITE) {
                    nip.putPixel(x,y,color);
                }
            }
        }
        
        IJ.run("Close");
        return true;
    }

    /**
     * Opens the EDM pruned skeleton, and generates a new
     * image with the skeleton overlayed in a particular color over the original.
     *  
     * <p>Pre: This class was setup appropriately with an image from the processed set of images.
     * <br />Post: A new image should be created the original color image with a graphical overlay.
     * @param color The 32-bit color to draw the overlay. 0xAABBCCDD where AA is the alpha, BB is the red channel, CC is the green channel, and DD is the green channel. 
     * @return Returns true on successful draw. False otherwise.
     */
    public boolean drawPrunedSkeletonOverlay(ImageProcessor nip, int color) {
        int x=0, y=0;
        
        // load the pruned skeleton
        IJ.open(directory+"pruned_"+originalName);
        ImagePlus prunedImage = IJ.getImage();
        ImageProcessor prunedIP = prunedImage.getProcessor();

        // draw the original image
        for(y=0; y<height; y++) {
            for(x=0; x<width; x++) {
                nip.putPixel(x,y,getColor(x,y));
            }
        }
        // draw the pruned skeleton in the given color
        for(y=0; y<height; y++) {
            for(x=0; x<width; x++) {
                if (prunedIP.getPixel(x,y) != WHITE) {
                    nip.putPixel(x,y,color);
                }
            }
        }
        
        IJ.run("Close");
        return true;
    }
    
    /**
     * Opens the original image and it's segmented image, and generates a new
     * image with the segmentation blended in a particular color over the original.
     *  
     * <p>Pre: This class was setup appropriately with an image from the processed set of images.
     * <br />Post: A new image should be created the original color image with a graphical overlay.
     * @param color The 32-bit color to draw the overlay. 0xAABBCCDD where AA is the alpha, BB is the red channel, CC is the green channel, and DD is the blue channel. 
     * @return Returns true on successful draw. False otherwise.
     * @see #red
     */
    public boolean drawBlendedSegmentationOverlay(ImageProcessor nip, int color) {
        int x=0, y=0;
        
        // load the blended segmented
        IJ.open(directory+"segmented_"+originalName);
        ImagePlus segmentedImage = IJ.getImage();
        ImageProcessor segmentedIP = segmentedImage.getProcessor();
        
        // draw the original image
        for(y=0; y<height; y++) {
            for(x=0; x<width; x++) {
                nip.putPixel(x,y,getColor(x,y));
            }
        }
        // draw the blended segment in the given color
        for(y=0; y<height; y++) {
            for(x=0; x<width; x++) {
                if (segmentedIP.getPixel(x,y) != WHITE) {
                    nip.putPixel(x,y,VascularNetworkToolkit.averageARGBcolor(color,getColor(x,y)));
                }
            }
        }
        
        IJ.run("Close");
        return true;
    }   

    /**
     * Opens the EDM outline, and generates a new image with the 
     * EDM generated outline overlayed in a particular color over the
     * original.
     *  
     * <p>Pre: This class was setup appropriately with an image from the processed set of images.
     * <br />Post: A new image should be created the original color image with a graphical overlay.
     * @param color The 32-bit color to draw the overlay. 0xAABBCCDD where AA is the alpha, BB is the red channel, CC is the green channel, and DD is the green channel. 
     * @return Returns true on successful draw. False otherwise.
     */
    public boolean drawOutlineSkeletonOverlay(ImageProcessor nip, int colorOutline, int colorSkeleton) {
        int x=0, y=0;
        
        // load the outline
        IJ.open(directory+"outline_"+originalName);
        ImagePlus outlineImage = IJ.getImage();
        ImageProcessor outlineIP = outlineImage.getProcessor();

        // load the pruned skeleton
        IJ.open(directory+"pruned_"+originalName);
        ImagePlus prunedImage = IJ.getImage();
        ImageProcessor prunedIP = prunedImage.getProcessor();

        // draw the original image
        for(y=0; y<height; y++) {
            for(x=0; x<width; x++) {
                nip.putPixel(x,y,getColor(x,y));
            }
        }
        // draw the outline in the given color
        for(y=0; y<height; y++) {
            for(x=0; x<width; x++) {
                if (outlineIP.getPixel(x,y) != WHITE) {
                    nip.putPixel(x,y,colorOutline);
                }
            }
        }

        // draw the pruned skeleton in the given color
        for(y=0; y<height; y++) {
            for(x=0; x<width; x++) {
                if (prunedIP.getPixel(x,y) != WHITE) {
                    nip.putPixel(x,y,colorSkeleton);
                }
            }
        }

        IJ.run("Close");
        IJ.run("Close");
        return true;
    }
}