| GelAnalyzer |

    package ij.plugin;
    import ij.*;
    import ij.gui.*;
    import ij.process.*;
    import ij.measure.*;
    import ij.plugin.filter.Analyzer;

    import java.awt.*;
    import java.awt.image.*;
    import java.awt.event.*;
    import java.util.*;

    /** This plugin generates gel profile plots that can be analyzed using
    the wand tool. It is similar to the "Gel Plotting Macros" in NIH Image. */
    public class GelAnalyzer implements PlugIn {

        static final String GEL = "gel.options";
        static final int OD=1, PERCENT=2, OUTLINE=4, INVERT=8;
        static int saveID;
        static int nLanes, saveNLanes;
        static Rectangle firstRect;
        static final int MAX_LANES = 100;
        static int[] x = new int[MAX_LANES+1];
        static PlotsCanvas plotsCanvas;
        static ImageProcessor ipLanes;
        static ImagePlus  gel;
        static int plotHeight;
        static int options = Prefs.getInt(GEL, PERCENT+INVERT);
        static boolean uncalibratedOD = (options&OD)!=0;
        static boolean labelWithPercentages = (options&PERCENT)!=0;;
        static boolean outlineLanes;
        static boolean invertPeaks = (options&INVERT)!=0;
        static Vector roiList;
        boolean invertedLut;

        ImagePlus imp;
        Font f;
        double odMin=Double.MAX_VALUE, odMax=-Double.MAX_VALUE;
        static boolean isVertical;

        public void run(String arg) {
            if (arg.equals("options")) {
                showDialog();
                return;
            }

            imp = WindowManager.getCurrentImage();
            if (imp==null) {
                IJ.noImage();
                return;
            }

            if (arg.equals("reset")) {
                nLanes = 0;
                saveNLanes = 0;
                saveID = 0;
                if (plotsCanvas!=null)
                    plotsCanvas.reset();
                ipLanes = null;
                roiList = null;
                if (gel!=null) {
                    ImageCanvas ic = gel.getCanvas();
                    if (ic!=null) ic.setDisplayList(null);
                    gel.draw();
                }
                return;
            }

            if (arg.equals("percent") && plotsCanvas!=null) {
                plotsCanvas.displayPercentages();
                return;
            }

            if (arg.equals("label") && plotsCanvas!=null) {
                if (plotsCanvas.counter==0)
                    show("There are no peak area measurements.");
                else
                    plotsCanvas.labelPeaks();
                return;
            }

            if (imp.getID()!=saveID) {
                nLanes=0;
                ipLanes = null;
                saveID = 0;
            }

            if (arg.equals("replot")) {
                if (saveNLanes==0) {
                    show("The data needed to re-plot the lanes is not available");
                    return;
                }
                nLanes = saveNLanes;
                plotLanes(gel, true);
                return;
            }

            if (arg.equals("draw")) {
                outlineLanes();
                return;
            }

            Roi roi = imp.getRoi();
            if (roi==null || roi.getType()!=Roi.RECTANGLE) {
                show("Rectangular selection required.");
                return;
            }
            Rectangle rect = roi.getBounds();
            if (nLanes==0) {
                invertedLut = imp.isInvertedLut();
                IJ.register(GelAnalyzer.class);  // keeps this class from being GC'd
            }

            if (arg.equals("first")) {
                selectFirstLane(rect);
                return;
            }

            if (nLanes==0) {
                show("You must first use the \"Outline First Lane\" command.");
                return;
            }
            if (arg.equals("next")) {
                selectNextLane(rect);
                return;
            }

            if (arg.equals("plot")) {
                if (( isVertical && (rect.x!=x[nLanes]) ) || ( !(isVertical) && (rect.y!=x[nLanes]) )) {
                    selectNextLane(rect);
                }
                plotLanes(gel, false);
                return;
            }

        }

        void showDialog() {
            GenericDialog gd = new GenericDialog("Gel Analyzer");
            gd.addCheckbox("Uncalibrated OD", uncalibratedOD);
            gd.addCheckbox("Label with Percentages", labelWithPercentages);
            gd.addCheckbox("Invert Peaks", invertPeaks);
            gd.showDialog();
            if (gd.wasCanceled())
                return;
            uncalibratedOD = gd.getNextBoolean();
            labelWithPercentages = gd.getNextBoolean();
            invertPeaks = gd.getNextBoolean();
            options = 0;
            if (uncalibratedOD) options |= OD;
            if (labelWithPercentages) options |= PERCENT;
            if (invertPeaks) options |= INVERT;
            return;
        }

        void selectFirstLane(Rectangle rect) {
            if (rect.width/rect.height>=2 || IJ.altKeyDown())
                isVertical = false;
            else
                isVertical = true;

            /*
            if ( (isVertical && (rect.height/rect.width)<2 ) || (!isVertical && (rect.width/rect.height)<2 ) ) {
                GenericDialog gd = new GenericDialog("Lane Orientation");
                String[] orientations = {"Vertical","Horizontal"};
                int defaultOrientation = isVertical?0:1;
                gd.addChoice("Lane Orientation:", orientations, orientations[defaultOrientation]);
                gd.showDialog();
                if (gd.wasCanceled())
                    return;
                String orientation = gd.getNextChoice();
                if(orientation.equals(orientations[0]))
                    isVertical=true;
                else
                    isVertical=false;
            }
            */

            IJ.showStatus("Lane 1 selected ("+(isVertical?"vertical":"horizontal")+" lanes)");
            firstRect = rect;
            nLanes = 1;
            saveNLanes = 0;
            if(isVertical)
                x[1] = rect.x;
            else
                x[1] = rect.y;
            gel = imp;
            saveID = imp.getID();
            roiList = null;
            updateRoiList(rect);
        }

        void selectNextLane(Rectangle rect) {
            if (rect.width!=firstRect.width || rect.height!=firstRect.height) {
                show("Selections must all be the same size.");
                return;
            }
            if (nLanes<MAX_LANES)
                nLanes += 1;
            IJ.showStatus("Lane " + nLanes + " selected");

            if(isVertical)
                x[nLanes] = rect.x;
            else
                x[nLanes] = rect.y;
            if (isVertical && rect.y!=firstRect.y) {
                rect.y = firstRect.y;
                gel.setRoi(rect);
            } else if (!isVertical && rect.x!=firstRect.x) {
                rect.x = firstRect.x;
                gel.setRoi(rect);
            }
            updateRoiList(rect);
        }

        void updateRoiList(Rectangle rect) {
                if (gel==null) return;
                if (roiList==null) {
                    roiList = new Vector();
                    ImageCanvas ic = gel.getCanvas();
                    if (ic!=null) ic.setDisplayList(roiList);
                }
                roiList.addElement(new Roi(rect.x, rect.y, rect.width, rect.height, null));
                gel.draw();
        }

        void plotLanes(ImagePlus imp, boolean replot) {
            int topMargin = 16;
            int bottomMargin = 2;
            double min = Double.MAX_VALUE;
            double max = -Double.MAX_VALUE;
            int plotWidth;
            double[][] profiles;
            profiles = new double[MAX_LANES+1][];
            IJ.showStatus("Plotting " + nLanes + " lanes");
            ImageProcessor ipRotated = imp.getProcessor();
            if(isVertical) {
                ipRotated = ipRotated.rotateLeft();
            }
            ImagePlus imp2 = new ImagePlus("", ipRotated);
            imp2.setCalibration(imp.getCalibration());
            if (uncalibratedOD && (imp2.getType()==ImagePlus.GRAY16 || imp2.getType()==ImagePlus.GRAY32))
                new ImageConverter(imp2).convertToGray8();
            if (invertPeaks) {
                    ImageProcessor ip2 = imp2.getProcessor().duplicate();
                    ip2.invert();
                    imp2.setProcessor(null, ip2);
            }
            //imp2.show();

            for (int i=1; i<=nLanes; i++) {
                if(isVertical)
                    imp2.setRoi(firstRect.y,
                                ipRotated.getHeight() - x[i] - firstRect.width,
                                firstRect.height, firstRect.width);
                else
                    imp2.setRoi(firstRect.x, x[i], firstRect.width, firstRect.height);
                ProfilePlot pp = new ProfilePlot(imp2);
                profiles[i] = pp.getProfile();
                if (pp.getMin()<min)
                    min = pp.getMin();
                if (pp.getMax()>max)
                    max = pp.getMax();
                if (uncalibratedOD)
                    profiles[i] = od(profiles[i]);
            }
            if (uncalibratedOD) {
                min = odMin;
                max = odMax;
            }

            if(isVertical)
                plotWidth = firstRect.height;
            else
                plotWidth = firstRect.width;
            if (plotWidth<500)
                plotWidth = 500;
            if(isVertical) {
                if (plotWidth>2*firstRect.height)
                    plotWidth = 2*firstRect.height;
            } else {
                if (plotWidth>2*firstRect.width)
                    plotWidth = 2*firstRect.width;
            }

            Dimension screen = IJ.getScreenSize();
            if (plotWidth>screen.width-40)
                plotWidth = screen.width - 40;
            plotHeight = plotWidth/2;
            if (plotHeight<200)
                plotHeight = 200;
            if (plotHeight>400)
                plotHeight = 400;
            ImageProcessor ip = new ByteProcessor(plotWidth, topMargin+nLanes*plotHeight+bottomMargin);
            ip.setColor(Color.white);
            ip.fill();
            ip.setColor(Color.black);
            //draw border
            int h= ip.getHeight();
            ip.moveTo(0,0);
            ip.lineTo(plotWidth-1,0);
            ip.lineTo(plotWidth-1, h-1);
            ip.lineTo(0, h-1);
            ip.lineTo(0, 0);
            ip.moveTo(0, h-2);
            ip.lineTo(plotWidth-1, h-2);
            String s = imp.getTitle()+"; ";
            Calibration cal = imp.getCalibration();
            if (cal.calibrated())
                s += cal.getValueUnit();
            else if (uncalibratedOD)
                s += "Uncalibrated OD";
            else
                s += "Uncalibrated";
            ip.moveTo(5,topMargin);
            ip.drawString(s);
            double xScale = (double)plotWidth/profiles[1].length;
            double yScale;
            if ((max-min)==0.0)
                yScale = 1.0;
            else
                yScale = plotHeight/(max-min);
            for (int i=1; i<=nLanes; i++) {
                double[] profile = profiles[i];
                int top = (i-1)*plotHeight + topMargin;
                int base = top+plotHeight;
                ip.moveTo(0, base);
                ip.lineTo((int)(profile.length*xScale), base);
                ip.moveTo(0, base-(int)((profile[0]-min)*yScale));
                for (int j = 1; j<profile.length; j++)
                    ip.lineTo((int)(j*xScale+0.5), base-(int)((profile[j]-min)*yScale+0.5));
            }
            Line.setWidth(1);
            ImagePlus plots = new Plots();
            plots.setProcessor("Plots of "+imp.getShortTitle(), ip);
            plots.changes = true;
            ip.setThreshold(0,0,ImageProcessor.NO_LUT_UPDATE); // Wand tool works better with threshold set
            if (cal.calibrated()) {
                double pixelsAveraged = isVertical?firstRect.width:firstRect.height;
                double scale = Math.sqrt((xScale*yScale)/pixelsAveraged);
                Calibration plotsCal = plots.getCalibration();
                plotsCal.setUnit("unit");
                plotsCal.pixelWidth = 1.0/scale;
                plotsCal.pixelHeight = 1.0/scale;
            }
            plots.show();
            saveNLanes = nLanes;
            nLanes = 0;
            saveID = 0;
            //gel = null;
            ipLanes = null;
            Toolbar toolbar = Toolbar.getInstance();
            toolbar.setColor(Color.black);
            toolbar.setTool(Toolbar.LINE);
            ImageWindow win = WindowManager.getCurrentWindow();
            ImageCanvas canvas = win.getCanvas();
            if (canvas instanceof PlotsCanvas)
                plotsCanvas = (PlotsCanvas)canvas;
            else
                plotsCanvas = null;
        }

        double[] od(double[] profile) {
            double v;
            for (int i=0; i<profile.length; i++) {
                v = 0.434294481*Math.log(255.0/(255.0-profile[i]));
                //v = 0.434294481*Math.log(255.0/v);
                if (v<odMin) odMin = v;
                if (v>odMax) odMax = v;
                profile[i] = v;
            }
            return profile;
        }

        void outlineLanes() {
            if (gel==null || roiList==null) {
                show("Data needed to outline lanes is no longer available.");
                return;
            }
            //IJ.write("outlining lane "+x);
            int lineWidth = (int)(1.0/gel.getCanvas().getMagnification());
            if (lineWidth<1)
                lineWidth = 1;
            Font f = new Font("Helvetica", Font.PLAIN, 12*lineWidth);
            ImageProcessor ip = gel.getProcessor();
            ImageProcessor ipLanes = ip.duplicate();
            if (!(ipLanes instanceof ByteProcessor))
                ipLanes = ipLanes.convertToByte(true);
            ipLanes.setFont(f);
            ipLanes.setLineWidth(lineWidth);
            setCustomLut(ipLanes);
            ImagePlus lanes = new ImagePlus("Lanes of "+gel.getShortTitle(), ipLanes);
            lanes.changes = true;
            lanes.setRoi(gel.getRoi());
            gel.killRoi();
            for (int i=0; i<roiList.size(); i++) {
                Roi roi = (Roi)roiList.elementAt(i);
                Rectangle r = roi.getBounds();
                ipLanes.drawRect(r.x, r.y, r.width, r.height);
                String s = ""+(i+1);
                if(isVertical) {
                    int yloc = r.y;
                    if (yloc<lineWidth*12) yloc += lineWidth*14;
                    ipLanes.drawString(s, r.x+r.width/2-ipLanes.getStringWidth(s)/2, yloc);
                } else {
                    int xloc = r.x-ipLanes.getStringWidth(s)-2;
                    if (xloc<lineWidth*10) xloc = r.x + 2;
                    ipLanes.drawString(s, xloc, r.y+r.height/2+6);
                }
            }
            lanes.killRoi();
            lanes.show();
        }

        void setCustomLut(ImageProcessor ip) {
            IndexColorModel cm = (IndexColorModel)ip.getColorModel();
            byte[] reds = new byte[256];
            byte[] greens = new byte[256];
            byte[] blues = new byte[256];
            cm.getReds(reds);
            cm.getGreens(greens);
            cm.getBlues(blues);
            reds[1] =(byte) 255;
            greens[1] = (byte)0;
            blues[1] = (byte)0;
            ip.setColorModel(new IndexColorModel(8, 256, reds, greens, blues));
            byte[] pixels = (byte[])ip.getPixels();
            for (int i=0; i<pixels.length; i++)
                if ((pixels[i]&255)==1)
                   pixels[i] = 0;
            ip.setColor(1);
        }

        void show(String msg) {
            IJ.showMessage("Gel Analyzer", msg);
        }

        /** Called once when ImageJ quits. */
        public static void savePreferences(Properties prefs) {
            prefs.put(GEL, Integer.toString(options));
        }

    }

    class Plots extends ImagePlus {

        /** Overrides ImagePlus.show(). */
        public void show() {
            img = ip.createImage();
            ImageCanvas ic = new PlotsCanvas(this);
            win = new ImageWindow(this, ic);
            IJ.showStatus("");
            if (ic.getMagnification()==1.0)
                return;
            while(ic.getMagnification()<1.0)
                ic.zoomIn(0,0);
            Point loc = win.getLocation();
            int w = getWidth()+20;
            int h = getHeight()+30;
            Dimension screen = IJ.getScreenSize();
            if (loc.x+w>screen.width)
                w = screen.width-loc.x-20;
            if (loc.y+h>screen.height)
                h = screen.height-loc.y-30;
            win.setSize(w, h);
            win.validate();
            repaintWindow();
        }

    }

    class PlotsCanvas extends ImageCanvas {

        public static final int MAX_PEAKS = 200;

        double[] actual = {428566.00,351368.00,233977.00,99413.00,60057.00,31382.00,
                           14531.00,7843.00,2146.00,752.00,367.00};
        double[] measured = new double[MAX_PEAKS];
        Rectangle[] rect = new Rectangle[MAX_PEAKS];
        int counter;

        public PlotsCanvas(ImagePlus imp) {
            super(imp);
        }

        public void mousePressed(MouseEvent e) {
            super.mousePressed(e);
            Roi roi = imp.getRoi();
            if (roi==null)
                return;
            if (roi.getType()==Roi.LINE)
                Roi.setColor(Color.blue);
            else
                Roi.setColor(Color.yellow);
            if (Toolbar.getToolId()!=Toolbar.WAND || IJ.spaceBarDown())
                return;
            ImageStatistics s = imp.getStatistics();
            if (counter==0)
                IJ.setColumnHeadings(" \tArea");
            double perimeter = roi.getLength();
            String error = "";
            double circularity = 4.0*Math.PI*(s.pixelCount/(perimeter*perimeter));
            if (circularity<0.025)
                error = " (error?)";
            double area = s.pixelCount+perimeter/2.0; // add perimeter/2 to account area under border
            Calibration cal = imp.getCalibration();
            area = area*cal.pixelWidth*cal.pixelHeight;
            rect[counter] = roi.getBounds();

            //area += (rect[counter].width/rect[counter].height)*1.5;
            // adjustment for small peaks from NIH Image gel macros

            int places = cal.scaled()?3:0;
            IJ.write((counter+1)+"\t"+IJ.d2s(area, places)+error);
            measured[counter] = area;
            if (counter<MAX_PEAKS)
                counter++;
        }

        public void mouseReleased(MouseEvent e) {
            super.mouseReleased(e);
            Roi roi = imp.getRoi();
            if (roi!=null && roi.getType()==Roi.LINE) {
                Undo.setup(Undo.FILTER, imp);
                imp.getProcessor().snapshot();
                roi.drawPixels();
                imp.updateAndDraw();
                imp.killRoi();
            }
        }

        void reset() {
            counter = 0;
        }

        void labelPeaks() {
            imp.killRoi();
            double total = 0.0;
            for (int i=0; i<counter; i++)
                total += measured[i];
            ImageProcessor ip = imp.getProcessor();
            ip.setFont(new Font("SansSerif", Font.PLAIN, 9));
            for (int i=0; i<counter; i++) {
                Rectangle r = rect[i];
                String s;
                if (GelAnalyzer.labelWithPercentages)
                    s = IJ.d2s((measured[i]/total)*100, 2);
                else
                    s = IJ.d2s(measured[i], 0);
                int swidth = ip.getStringWidth(s);
                int x = r.x + r.width/2 - swidth/2;
                int y = r.y + r.height*3/4 + 9;
                int[] data = new int[swidth];
                ip.getRow(x, y, data, swidth);
                boolean fits = true;
                for (int j=0; j<swidth; j++)
                    if (data[j]!=255) {
                        fits = false;
                        break;
                    }
                fits = fits && measured[i]>500;
                if (r.height>=(GelAnalyzer.plotHeight-11))
                    fits = true;
                if (!fits)
                    y = r.y - 2;
                ip.drawString(s, x, y);
                //IJ.write(i+": "+x+" "+y+" "+s+" "+ip.StringWidth(s)/2);
            }
            imp.updateAndDraw();
            displayPercentages();
            //Toolbar.getInstance().setTool(Toolbar.RECTANGLE);
            reset();
        }

        void displayPercentages() {
            IJ.setColumnHeadings(" \tarea\tpercent");
            double total = 0.0;
            for (int i=0; i<counter; i++)
                total += measured[i];
            if (IJ.debugMode && counter==actual.length) {
                debug();
                return;
            }
            for (int i=0; i<counter; i++) {
                double percent = (measured[i]/total)*100;
                IJ.write((i+1)+"\t"+IJ.d2s(measured[i],3)+"\t"+IJ.d2s(percent,3));
            }
        }

        void debug() {
            for (int i=0; i<counter; i++) {
                double a = (actual[i]/actual[0])*100;
                double m = (measured[i]/measured[0])*100;
                IJ.write(IJ.d2s(a, 4)+" "
                         +IJ.d2s(m, 4)+" "
                         +IJ.d2s(((m-a)/m)*100, 4));
            }
        }

    }

| GelAnalyzer |