| Calibrator |

    package ij.plugin.filter;
    import ij.*;
    import ij.gui.*;
    import ij.process.*;
    import ij.measure.*;
    import ij.util.*;
    import ij.io.*;
    import ij.plugin.TextReader;
    import java.awt.*;
    import java.util.*;
    import java.awt.event.*;
    import java.io.*;

    /** Implements the Analyze/Calibrate command. */
    public class Calibrator implements PlugInFilter, Measurements, ActionListener {

        private static final String NONE = "None";
        private static final String INVERTER = "Pixel Inverter";
        private static final String UNCALIBRATED_OD = "Uncalibrated OD";
        private static final String CUSTOM = "Custom";
        private static boolean showSettings;
        private boolean global1, global2;
        private ImagePlus imp;
        private int choiceIndex;
        private String[] functions;
        private int nFits = CurveFitter.fitList.length;
        private int spacerIndex = nFits+1;
        private int inverterIndex = nFits+2;
        private int odIndex = nFits+3;
        private int customIndex = nFits+4;
        private static String xText = "";
        private static String yText = "";
        private static boolean importedValues;
        private String unit;
        private double lx=0.02, ly=0.1;
        private int oldFunction;
        private String sumResiduals, fitGoodness;
        private Button open, save;
        private GenericDialog gd;

        public int setup(String arg, ImagePlus imp) {
            this.imp = imp;
            IJ.register(Calibrator.class);
            return DOES_ALL-DOES_RGB+NO_CHANGES;
        }

        public void run(ImageProcessor ip) {
            global1 = imp.getGlobalCalibration()!=null;
            if (!showDialog(imp))
                return;
            if (choiceIndex==customIndex) {
                showPlot(null, null, imp.getCalibration(), null);
                return;
            } else if (imp.getType()==ImagePlus.GRAY32) {
                if (choiceIndex==0)
                    imp.getCalibration().setValueUnit(unit);
                else
                    IJ.error("Calibrate", "Function must be \"None\" for 32-bit images,\nbut you can change the Unit.");
            } else
                calibrate(imp);
        }

        public boolean showDialog(ImagePlus imp) {
            String defaultChoice;
            Calibration cal = imp.getCalibration();
            functions = getFunctionList(cal.getFunction()==Calibration.CUSTOM);
            int function = cal.getFunction();
            oldFunction = function;
            double[] p = cal.getCoefficients();
            unit = cal.getValueUnit();
            if (function==Calibration.NONE)
                defaultChoice=NONE;
            else if (function<nFits&&function==Calibration.STRAIGHT_LINE&&p!=null&& p[0]==255.0&&p[1]==-1.0)
                defaultChoice=INVERTER;
            else if (function<nFits)
                defaultChoice = CurveFitter.fitList[function];
            else if (function==Calibration.UNCALIBRATED_OD)
                defaultChoice=UNCALIBRATED_OD;
            else if (function==Calibration.CUSTOM)
                defaultChoice=CUSTOM;
            else
                defaultChoice=NONE;

            String tmpText = getMeans();
            if (!importedValues)
                xText = tmpText;
            gd = new GenericDialog("Calibrate...");
            gd.addChoice("Function:", functions, defaultChoice);
            gd.addStringField("Unit:", unit, 16);
            gd.addTextAreas(xText, yText, 20, 14);
            //gd.addMessage("Left column contains uncalibrated measured values,\n right column contains known values (e.g., OD).");
            gd.addPanel(makeButtonPanel(gd));
            gd.addCheckbox("Global Calibration", global1);
            //gd.addCheckbox("Show Simplex Settings", showSettings);
            gd.showDialog();
            if (gd.wasCanceled())
                return false;
            else {
                choiceIndex = gd.getNextChoiceIndex();
                unit = gd.getNextString();
                xText = gd.getNextText();
                yText = gd.getNextText();
                global2 = gd.getNextBoolean();
                //showSettings = gd.getNextBoolean();
                return true;
            }
        }

        /** Creates a panel containing "Save..." and "Save..." buttons. */
        Panel makeButtonPanel(GenericDialog gd) {
            Panel buttons = new Panel();
            buttons.setLayout(new FlowLayout(FlowLayout.CENTER, 5, 0));
            open = new Button("Open...");
            open.addActionListener(this);
            buttons.add(open);
            save = new Button("Save...");
            save.addActionListener(this);
            buttons.add(save);
            return buttons;
        }

        public void calibrate(ImagePlus imp) {
            Calibration cal = imp.getCalibration();
            Calibration calOrig = cal.copy();
            int function = Calibration.NONE;
            boolean is16Bits = imp.getType()==ImagePlus.GRAY16;
            double[] parameters = null;
            double[] x=null, y=null;
            boolean zeroClip=false;
            if (choiceIndex<=0) {
                if (oldFunction==Calibration.NONE&&!yText.equals("")&&!xText.equals(""))
                    IJ.error("Calibrate", "Please select a function");
                function = Calibration.NONE;
            } else if (choiceIndex<=nFits) {
                function = choiceIndex - 1;
                //if (function>0 && is16Bits) {
                //  IJ.error("Calibrate", "Calibration of 16-bit images, except with straight\nline functions, is currently not supported.");
                //  return;
                //}
                x = getData(xText);
                y = getData(yText);
                if (!cal.calibrated() || y.length!=0 || function!=oldFunction) {
                    parameters = doCurveFitting(x, y, function);
                    if (parameters==null)
                        return;
                }
                zeroClip = true;
                for (int i=0; i<y.length; i++)
                    if (y[i]<0.0) zeroClip = false;
            } else if (choiceIndex==inverterIndex) {
                function = Calibration.STRAIGHT_LINE;
                parameters = new double[2];
                if (is16Bits)
                    parameters[0] = 65535;
                else
                    parameters[0] = 255;
                parameters[1] = -1.0;
                unit = "Inverted Gray Value";
            } else if (choiceIndex==odIndex) {
                if (is16Bits) {
                    IJ.error("Calibrate", "Uncalibrated OD is not supported on 16-bit images.");
                    return;
                }
                function = Calibration.UNCALIBRATED_OD;
                unit = "Uncalibrated OD";
            }
            cal.setFunction(function, parameters, unit, zeroClip);
            if (!cal.equals(calOrig))
                imp.setCalibration(cal);
            imp.setGlobalCalibration(global2?cal:null);
            if (global2 || global2!=global1)
                WindowManager.repaintImageWindows();
            else
                imp.repaintWindow();
            if (function!=Calibration.NONE)
                showPlot(x, y, cal, fitGoodness);
        }

        double[] doCurveFitting(double[] x, double[] y, int fitType) {
            if (x.length!=y.length || y.length==0) {
                IJ.error("Calibrate",
                    "To create a calibration curve, the left column must\n"
                    +"contain a list of measured mean pixel values and the\n"
                    +"right column must contain the same number of calibration\n"
                    +"standard values. Use the Measure command to add mean\n"
                    +"pixel value measurements to the left column.\n"
                    +" \n"
                    +"    Left column: "+x.length+" values\n"
                    +"    Right column: "+y.length+" values\n"
                    );
                return null;
            }
            int n = x.length;
            double xmin=0.0,xmax;
            if (imp.getType()==ImagePlus.GRAY16)
                xmax=65535.0;
            else
                xmax=255.0;
            double[] a = Tools.getMinMax(y);
            double ymin=a[0], ymax=a[1];
            CurveFitter cf = new CurveFitter(x, y);
            cf.doFit(fitType, showSettings);
            int np = cf.getNumParams();
            double[] p = cf.getParams();
            fitGoodness = IJ.d2s(cf.getRSquared(),6);
            double[] parameters = new double[np];
            for (int i=0; i<np; i++)
                parameters[i] = p[i];
            return parameters;
        }

        void showPlot(double[] x, double[] y, Calibration cal, String rSquared) {
            if (!cal.calibrated())
                return;
            int xmin,xmax,range;
            float[] ctable = cal.getCTable();
            if (ctable.length==256) { //8-bit image
                xmin = 0;
                xmax = 255;
            } else {  // 16-bit image
                xmin = 0;
                xmax = 65535;
            }
            range = 256;
            float[] px = new float[range];
            float[] py = new float[range];
            for (int i=0; i<range; i++)
                px[i]=(float)((i/255.0)*xmax);
            for (int i=0; i<range; i++)
                py[i]=ctable[(int)px[i]];
            double[] a = Tools.getMinMax(py);
            double ymin = a[0];
            double ymax = a[1];
            int fit = cal.getFunction();
            String unit = cal.getValueUnit();
            PlotWindow pw = new PlotWindow("Calibration Function","pixel value",unit,px,py);
            pw.setLimits(xmin,xmax,ymin,ymax);
            if (x!=null&&y!=null&&x.length>0&&y.length>0)
                pw.addPoints(x, y, PlotWindow.CIRCLE);
            double[] p = cal.getCoefficients();
            if (fit<=Calibration.LOG2) {
                drawLabel(pw, CurveFitter.fList[fit]);
                ly += 0.04;
            }
            if (p!=null) {
                int np = p.length;
                drawLabel(pw, "a="+IJ.d2s(p[0],6));
                drawLabel(pw, "b="+IJ.d2s(p[1],6));
                if (np>=3)
                    drawLabel(pw, "c="+IJ.d2s(p[2],6));
                if (np>=4)
                    drawLabel(pw, "d="+IJ.d2s(p[3],6));
                if (np>=5)
                    drawLabel(pw, "e="+IJ.d2s(p[4],6));
                ly += 0.04;
            }
            if (rSquared!=null)
                {drawLabel(pw, "R^2="+rSquared); rSquared=null;}
            pw.draw();
        }

        void drawLabel(PlotWindow pw, String label) {
            pw.addLabel(lx, ly, label);
            ly += 0.08;
        }

        double sqr(double x) {return x*x;}

        String[] getFunctionList(boolean custom) {
            int n = nFits+4;
            if (custom) n++;
            String[] list = new String[n];
            list[0] = NONE;
            for (int i=0; i<nFits; i++)
                list[1+i] = CurveFitter.fitList[i];
            list[spacerIndex] = "-";
            list[inverterIndex] = INVERTER;
            list[odIndex] = UNCALIBRATED_OD;
            if (custom)
                list[customIndex] = CUSTOM;
            return list;
        }

        String getMeans() {
            float[] umeans = Analyzer.getUMeans();
            int count = Analyzer.getCounter();
            if (umeans==null || count==0)
                return "";
            if (count>MAX_STANDARDS)
                count = MAX_STANDARDS;
            String s = "";
            for (int i=0; i<count; i++)
                s += IJ.d2s(umeans[i],2)+"\n";
            importedValues = false;
            return s;
        }

        double[] getData(String xData) {
            StringTokenizer st = new StringTokenizer(xData);
            int nTokens = st.countTokens();
            if (nTokens<1)
                return new double[0];
            int n = nTokens;
            double data[] = new double[n];
            for (int i=0; i<n; i++) {
                data[i] = getNum(st);
            }
            return data;
        }

        double getNum(StringTokenizer st) {
            Double d;
            String token = st.nextToken();
            try {d = new Double(token);}
            catch (NumberFormatException e){d = null;}
            if (d!=null)
                return(d.doubleValue());
            else
                return 0.0;
        }

        void save() {
            TextArea ta1 = gd.getTextArea1();
            TextArea ta2 = gd.getTextArea2();
            ta1.selectAll();
            String text1 = ta1.getText();
            ta1.select(0, 0);
            ta2.selectAll();
            String text2 = ta2.getText();
            ta2.select(0, 0);
            double[] x = getData(text1);
            double[] y = getData(text2);
            SaveDialog sd = new SaveDialog("Save as Text...", "calibration", ".txt");
            String name = sd.getFileName();
            if (name == null)
                return;
            String directory = sd.getDirectory();
            PrintWriter pw = null;
            try {
                FileOutputStream fos = new FileOutputStream(directory+name);
                BufferedOutputStream bos = new BufferedOutputStream(fos);
                pw = new PrintWriter(bos);
            }
            catch (IOException e) {
                IJ.error("" + e);
                return;
            }
            IJ.wait(250);  // give system time to redraw ImageJ window
            int n = Math.max(x.length, y.length);
            for (int i=0; i<n; i++) {
                String xs = x.length==0?"":i<x.length?""+x[i]:"0";
                String ys = y.length==0?"":i<y.length?""+y[i]:"0";
                pw.println(xs + "\t"+ ys);
            }
            pw.close();
        }

        void open() {
            OpenDialog od = new OpenDialog("Open Calibration...", "");
            String directory = od.getDirectory();
            String name = od.getFileName();
            if (name==null)
                return;
            String path = directory + name;
            TextReader tr = new TextReader();
            ImageProcessor ip = tr.open(path);
            if (ip==null)
                return;
            int width = ip.getWidth();
            int height = ip.getHeight();
            if (!((width==1||width==2)&&height>1)) {
                IJ.error("Calibrate", "This appears to not be a one or two column text file");
                return;
            }
            StringBuffer sb = new StringBuffer();
            for (int i=0; i<height; i++) {
                sb.append(""+ip.getPixelValue(0, i));
                sb.append("\n");
            }
            String s1=null, s2=null;
            if (width==2) {
                s1 = new String(sb);
                sb = new StringBuffer();
                for (int i=0; i<height; i++) {
                    sb.append(""+ip.getPixelValue(1, i));
                    sb.append("\n");
                }
                s2 = new String(sb);
            } else
                s2 = new String(sb);
            if (s1!=null) {
                TextArea ta1 = gd.getTextArea1();
                ta1.selectAll();
                ta1.setText(s1);
            }
            TextArea ta2 = gd.getTextArea2();
            ta2.selectAll();
            ta2.setText(s2);
            importedValues = true;
        }

        public void actionPerformed(ActionEvent e) {
            Object source = e.getSource();
            if (source==save)
                save();
            else if (source==open)
                open();
        }

    }

| Calibrator |