| SurfacePlotter |

    package ij.plugin;
    import ij.*;
    import ij.plugin.filter.*;
    import ij.process.*;
    import ij.gui.*;
    import java.awt.*;
    import java.awt.image.*;
    import java.math.*;
    import java.util.*;
    import ij.measure.*;

    public class SurfacePlotter implements PlugIn {

        static int plotWidth = 350;
        static int polygonMultiplier = 100;
        static boolean oneToOne;
        static boolean firstTime = true;

        static boolean showWireframe=false;
        static boolean showGrayscale=true;
        static boolean showAxis=true;
        static boolean whiteBackground=false;
        static boolean blackFill=false;
        static boolean smooth = true;

        ImagePlus img;
        int[] x,y;
        boolean invertedLut;
        double angleInDegrees = 35;
        double angle = (angleInDegrees/360.0)*2.0*Math.PI;
        double angle2InDegrees = 15.0;
        double angle2 = (angle2InDegrees/360.0)*2.0*Math.PI;
        double yinc2 = Math.sin(angle2);
        double p1x, p1y;  // left bottom corner
        double p2x, p2y;  // center bottom corner
        double p3x, p3y;  // right  bottom corner

        LookUpTable lut;

        public void run(String arg) {
            img = WindowManager.getCurrentImage();
            if (img==null)
                {IJ.noImage(); return;}
            if (img.getType()==ImagePlus.COLOR_RGB)
                {IJ.error("Surface Plotter", "Grayscale or pseudo-color image required"); return;}
            invertedLut = img.getProcessor().isInvertedLut();
            if (firstTime) {
                if (invertedLut)
                    whiteBackground = true;
                firstTime = false;
            }
            if (!showDialog())
                return;

            int stackFlags = IJ.setupDialog(img, 0);
            if(stackFlags == PlugInFilter.DONE)
                return;
            Date start = new Date();
            lut = img.createLut();

            if(stackFlags == PlugInFilter.DOES_STACKS && img.getStack().getSize()>1){
                ImageStack stackSource = img.getStack();
                ImageProcessor ip = stackSource.getProcessor(1);
                ImageProcessor plot = makeSurfacePlot(ip);
                ImageStack stack = new ImageStack(plot.getWidth(), plot.getHeight());
                stack.setColorModel(plot.getColorModel());
                for(int i=1;i<=stackSource.getSize();i++)
                    stack.addSlice(null, new byte[plot.getWidth()* plot.getHeight()]);
                stack.setPixels(plot.getPixels(), 1);
                ImagePlus plots = new ImagePlus("Surface Plot", stack);
                plots.show();
                for(int i=2;i<=stackSource.getSize();i++) {
                    IJ.showStatus("Drawing slice " + i + "..." + " (" + (100*(i-1)/stackSource.getSize()) + "% done)");
                    ip = stackSource.getProcessor(i);
                    plot = makeSurfacePlot(ip);
                    ImageWindow win = plots.getWindow();
                    if (win!=null && win.isClosed()) break;
                    stack.setPixels(plot.getPixels(), i);
                    plots.setSlice(i);
                }
            } else {
                ImageProcessor plot = makeSurfacePlot(img.getProcessor());
                new ImagePlus("Surface Plot", plot).show();
            }

            Date end = new Date();
                long lstart = start.getTime();
                long lend = end.getTime();
                long difference = lend - lstart;
            IJ.register(SurfacePlotter.class);
            IJ.showStatus("Done in "+difference+" msec." );
        }

        boolean showDialog() {
            GenericDialog gd = new GenericDialog("Surface Plotter");
            //gd.addNumericField("Plot Width (pixels):", plotWidth, 0);
            //gd.addNumericField("Angle (-90-90 degrees):", angleInDegrees, 0);
            gd.addNumericField("Polygon Multiplier (10-200%):", polygonMultiplier, 0);
            gd.addCheckbox("Draw Wireframe", showWireframe);
            gd.addCheckbox("Shade", showGrayscale);
            gd.addCheckbox("Draw Axis", showAxis);
            gd.addCheckbox("Source Background is Lighter", whiteBackground);
            gd.addCheckbox("Fill Plot Background with Black", blackFill);
            gd.addCheckbox("One Polygon Per Line", oneToOne);
            gd.addCheckbox("Smooth", smooth);
            gd.showDialog();
            if (gd.wasCanceled())
                return false;
            //plotWidth = (int) gd.getNextNumber();
            //angleInDegrees = gd.getNextNumber();
            polygonMultiplier = (int)gd.getNextNumber();
            showWireframe = gd.getNextBoolean();
            showGrayscale = gd.getNextBoolean();
            showAxis = gd.getNextBoolean();
            whiteBackground = gd.getNextBoolean();
            blackFill = gd.getNextBoolean();
            oneToOne = gd.getNextBoolean();
            smooth = gd.getNextBoolean();
            if (showWireframe && !showGrayscale)
                blackFill = false;
            if (polygonMultiplier>400) polygonMultiplier = 400;
            if (polygonMultiplier<10) polygonMultiplier = 10;
            return true;
        }

        public ImageProcessor makeSurfacePlot(ImageProcessor ip) {
            ip = ip.duplicate();
            Rectangle roi = img.getProcessor().getRoi();
            ip.setRoi(roi);
            if (!(ip instanceof ByteProcessor)) {
                ip.setMinAndMax(img.getProcessor().getMin(), img.getProcessor().getMax());
                ip = ip.convertToByte(true);
                ip.setRoi(roi);
            }
            double angle = (angleInDegrees/360.0)*2.0*Math.PI;
            int polygons = (int)(plotWidth*(polygonMultiplier/100.0)/4);
            if (oneToOne)
                polygons = roi.height;
            double xinc = 0.8*plotWidth*Math.sin(angle)/polygons;
            double yinc = 0.8*plotWidth*Math.cos(angle)/polygons;
            IJ.showProgress(0.01);
            ip.setInterpolate(!oneToOne);
            ip = ip.resize(plotWidth, polygons);
            int width = ip.getWidth();
            int height = ip.getHeight();
            double min = ip.getMin();
            double max = ip.getMax();

            if(invertedLut) ip.invert();
            if(whiteBackground) ip.invert();
            if (smooth) ip.smooth();

            x = new int[width+2];
            y = new int[width+2];
            double xstart = 10.0;
            if (xinc<0.0)
                xstart += Math.abs(xinc)*polygons;
            ByteProcessor ipProfile =new ByteProcessor(width, (int)(256+width*yinc2));
            ipProfile.setValue(255);
            ipProfile.fill();
            double ystart =  yinc2*width;
            int ybase = (int)(ystart+0.5);
            int windowWidth =(int)(plotWidth+polygons*Math.abs(xinc) + 20.0);
            int windowHeight = (int)(ipProfile.getHeight()+polygons*yinc + 10.0);

            if(showAxis){
                xstart += 50+20;
                ystart += 10;
                windowWidth += 60+20;
                windowHeight += 20;
                p1x = xstart;
                p1y = ystart+255;
                p2x = xstart+xinc*height;;
                p2y = p1y+yinc*height;
                p3x =  p2x+width-1;
                p3y = p2y- yinc2*width;
            }

            if(showGrayscale) {
                int v;
                int[] column = new int[255];
                for(int row=0; row<255; row++) {
                    if(whiteBackground)
                        v = row;
                    else
                        v = 255-row;
                    column[row] = v;
                }
                int base = ipProfile.getHeight()-255;
                for(int col=0; col<width; col++) {
                    ipProfile.putColumn(col, base-(int)(yinc2*col+0.5), column, 255);
                }
            } else {
                ipProfile.setValue(254);
                ipProfile.fill();
            }

            ipProfile.snapshot();

            ImageProcessor ip2 = new ByteProcessor(windowWidth, windowHeight);
            if(showGrayscale) {
                ip2.setColorModel(ip.getColorModel());
                if(invertedLut)
                    ip2.invertLut();
                fixLut(ip2);
            }
            if(!blackFill)
                ip2.setValue(255);
            else
                ip2.setValue(0);
            ip2.fill();

            for (int row=0; row<height; row++) {
                double[] profile = ip.getLine(0, row, width-1, row);
                clearAboveProfile(ipProfile, profile, width, yinc2);
                int ixstart = (int)(xstart+0.5);
                int iystart = (int)(ystart+0.5);

                ip2.copyBits(ipProfile, ixstart, iystart-ybase, Blitter.COPY_TRANSPARENT);
                ipProfile.reset();

                if (showWireframe) {
                    ip2.setValue(0);
                    double ydelta = 0.0;
                    ip2.moveTo(ixstart, (int)(ystart+255.5 - profile[0]) );
                    for(int i=1; i<width; i++) {
                        ydelta += yinc2;
                        ip2.lineTo( ixstart+i,  (int) (ystart+255.5-(profile[i]+ydelta)));
                    }
                    ip2.drawLine(ixstart, iystart+255, ixstart + width-1, (int)( ystart+255.5-ydelta) );
                    ip2.drawLine( ixstart, iystart+255-(int) (profile[0]+0.5), ixstart, iystart+255 );
                    ip2.drawLine( ixstart+width-1, (int) ( ystart+255.5-ydelta),  ixstart+width-1, (int) (ystart+255.5-(profile[width-1]+ydelta)) );
                }

                xstart += xinc;
                ystart += yinc;
                if ((row%10)==0) IJ.showProgress((double)row/height);
            }

            IJ.showProgress(1.0);

            if(invertedLut) {
                ip.invert();
                ip.invertLut();
            }
            if(whiteBackground)
                ip.invert();

            if (showAxis) {
                if (!lut.isGrayscale() && showGrayscale)
                    ip2 = ip2.convertToRGB();
                drawAndLabelAxis(ip, ip2, roi);
            }

            if (img.getStackSize()==1)
                ip2 = trimPlot(ip2, ybase);

            return ip2;
        }

        void drawAndLabelAxis(ImageProcessor ip, ImageProcessor ip2, Rectangle roi) {
            if(!blackFill)
                ip2.setColor(Color.black);
            else
                ip2.setColor(Color.white);
            ip2.setAntialiasedText(true);
            String s;
            int w, h;
            Calibration cal = img.getCalibration();

            //z-axis & label
            s = cal.getValueUnit();
            if (s.equals("Gray Value"))
                s = "";
            w =  ip2.getFontMetrics().stringWidth(s);
            drawAxis(ip2, (int) p1x, (int) p1y-255, (int) p1x, (int) p1y , s, 10, -1, 0, 1);
            double min, max;
            if (img.getBitDepth()==8) {
                min = 0;
                max = 255;
            } else {
                min = img.getProcessor().getMin();
                max = img.getProcessor().getMax();
            }
            //IJ.log("");
            //IJ.log(min+"  "+max+"  "+cal.getCValue((int)min)+"  "+cal.getCValue((int)max));
            //ip2.putPixelValue(0,0,0);
            //boolean zeroIsBlack
            //IJ.log(ip2.getPixelValue(+"  "+max+"  "+cal.getCValue((int)min)+"  "+cal.getCValue((int)max));
            if (cal.calibrated()) {
                min = cal.getCValue((int)min);
                max = cal.getCValue((int)max);
            }
            //if (invertedPixelValues)
            //  {double t=max; max=min; min=t;}
            ip2.setAntialiasedText(true);
            s = String.valueOf( (double) Math.round(max*10)/10);
            w =  ip.getFontMetrics().stringWidth(s);
            h =  ip.getFontMetrics().getHeight();
            ip2.drawString(s, (int) p1x-15-w, (int) p1y-255 +h/2);  //ybase+5+h+(int ( yinc2/xinc *10));
            s = String.valueOf( (double) Math.round(min*10)/10);
            w =  ip2.getFontMetrics().stringWidth(s);
            ip2.drawString(s, (int) p1x-15-w, (int) p1y +h/2);

            //x-axis
            s = (double) Math.round(roi.height*cal.pixelHeight*10)/10+" "+cal.getUnits();
            w =  ip2.getFontMetrics().stringWidth(s);
            drawAxis(ip2, (int) p1x, (int) p1y, (int) p2x, (int) p2y, s, 10, -1, 1, 1);

            //y-axis
            s =  (double) Math.round(roi.width*cal.pixelWidth*10)/10+" "+cal.getUnits();
            w =  ip2.getFontMetrics().stringWidth(s);
            //drawAxis(ip2, (int) p2x, (int) p2y, (int) p3x, (int) p3y, s, 10, 1 , 1, -1);
            drawAxis(ip2, (int) p2x, (int) p2y, (int) p3x, (int) p3y, s, 10, 1, -1, 1);

        }

        void drawAxis(ImageProcessor ip, int x1, int y1, int x2, int y2, String label, int offset, int offsetXDirection, int offsetYDirection, int labelSide){
            if(blackFill)
                ip.setColor(Color.white);
            else
                ip.setColor(Color.black);

            double m = -(double) (y2-y1)/(double) (x2-x1);

            if(m==0)
                m=.0001;
            double mTangent = -1/m;
            double theta = Math.atan(mTangent);

            int dy = -offsetXDirection * (int) ( 7*Math.sin(theta) );
            int dx = -offsetXDirection * (int) ( 7*Math.cos(theta) );

            x1 += offsetXDirection * (int) ( offset*Math.cos(theta) );
            x2 += offsetXDirection * (int) ( offset*Math.cos(theta) );

            y1 += offsetYDirection * (int) ( offset*Math.sin(theta) );
            y2 += offsetYDirection * (int) ( offset*Math.sin(theta) );

            ip.drawLine(x1, y1, x2, y2);

            ip.drawLine(x1, y1, x1+dx, y1-dy);
            ip.drawLine(x2, y2, x2+dx, y2-dy);
            ImageProcessor ipText = drawString( ip, label, (int) (Math.atan(m)/2/Math.PI*360) );
            if(blackFill)
                ipText.invert();

            Blitter b;
            if (ip instanceof ByteProcessor)
                b = new ByteBlitter((ByteProcessor) ip);
            else
                b = new ColorBlitter((ColorProcessor) ip);
            Color c = blackFill?Color.black:Color.white;
            b.setTransparentColor(c);
            int xloc = (x1+x2)/2-ipText.getWidth()/2  + offsetXDirection*labelSide*(int)(15*Math.cos(theta));
            int yloc = (y1+y2)/2-ipText.getHeight()/2 + offsetYDirection*labelSide*(int)(15*Math.sin(theta));
            b.copyBits(ipText, xloc, yloc, Blitter.COPY_TRANSPARENT);

            return;
        }

        ImageProcessor drawString(ImageProcessor ip, String s, int a){
            int w =  ip.getFontMetrics().stringWidth(s);
            int h =  ip.getFontMetrics().getHeight();
            int ipW, ipH;

            double r = Math.sqrt( (w/2)*(w/2) + (h/2)*(h/2) );
            double aR = (a/360.0)*2.0*Math.PI;
            double aBaseR = Math.acos( (w/2)/r );

            ipW = (int) Math.abs(r*Math.cos(aBaseR+aR));
            ipH = (int) Math.abs(r*Math.sin(aBaseR+aR));

            if((int) Math.abs(r*Math.cos(-aBaseR+aR))>ipW)
                ipW = (int) Math.abs(r*Math.cos(-aBaseR+aR));
            if((int) Math.abs(r*Math.sin(-aBaseR+aR))>ipH)
                ipH = (int) Math.abs(r*Math.sin(-aBaseR+aR));

            ipW *= 2;
            ipH *= 2;

            int tW = w;
            if(ipW>w)
                tW = ipW;
            ImageProcessor ipText = new ByteProcessor(tW, ipH);
            ipText.setColor(Color.white);
            ipText.fill();
            ipText.setColor(Color.black);
            ipText.setAntialiasedText(true);
            ipText.drawString(s, tW/2-w/2, ipH/2+h/2);
            ipText.setInterpolate(true);
            ipText.rotate(-a);
            ipText.setRoi(tW/2-ipW/2, 0, ipW, ipH);
            ipText = ipText.crop();

            //new ImagePlus("test", ipText).show();
            //ip.copyBits(ipText, x, y, Blitter.COPY_TRANSPARENT);

            return ipText;
        }

        void clearAboveProfile(ImageProcessor ipProfile, double[] profile, int width, double yinc2) {
            byte[] pixels = (byte[])ipProfile.getPixels();
            double ydelta = 0.0;
            int height = ipProfile.getHeight();
            for(int x=0; x<width; x++) {
                ydelta += yinc2;
                int top = height - (int)(profile[x]+ydelta);
                for (int y=0,index=x; y<top; y++, index+=width)
                    pixels[index] = (byte)255;
            }
        }

        ImageProcessor trimPlot(ImageProcessor plot, int maxTrim) {
            int background = plot.getPixel(0, 0);
            int width = plot.getWidth();
            int height = plot.getHeight();
            int trim = maxTrim-5;
            a: for (int y=0; y<(maxTrim-5); y++)
                for (int x=0; x<width; x++)
                    if (plot.getPixel(x,y)!=background)
                        {trim = y-5; break a;}
            if (trim>10) {
                plot.setRoi(0, trim, width, height-trim);
                plot = plot.crop();
            }
            return plot;
        }

        void fixLut(ImageProcessor ip) {
            if(!lut.isGrayscale() && lut.getMapSize() == 256){

                for(int y=0;y<ip.getHeight();y++){
                    for(int x=0;x<ip.getWidth();x++){
                        if(ip.getPixelValue(x, y)==0){
                            ip.putPixelValue(x, y, 1);
                        }else if(ip.getPixelValue(x, y)==255){
                            ip.putPixelValue(x, y, 254);
                        }

                    }
                }

                byte[] rLUT = lut.getReds();            //new byte[256];
                byte[] gLUT = lut.getGreens();          //new byte[256];
                byte[] bLUT = lut.getBlues();           //new byte[256];

                rLUT[0] = (byte)0;
                gLUT[0] = (byte)0;
                bLUT[0] = (byte)0;
                rLUT[255] = (byte)255;
                gLUT[255] = (byte)255;
                bLUT[255] = (byte)255;

                ip.setColorModel(new IndexColorModel(8, 256, rLUT, gLUT, bLUT));

            }
        }

    }

| SurfacePlotter |