package ij.plugin.frame;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.io.*;
import java.awt.datatransfer.*;
import ij.*;
import ij.plugin.PlugIn;
import ij.plugin.frame.*;
import ij.text.*;
import ij.gui.*;
import ij.util.*;
import ij.io.*;
import ij.process.*;
import ij.measure.*;
public class Fitter extends PlugInFrame implements PlugIn, ItemListener, ActionListener, KeyListener, ClipboardOwner {
Choice fit;
Button doIt, open, apply;
Checkbox settings;
String fitTypeStr = CurveFitter.fitList[0];
TextArea textArea;
double[] dx = {0,1,2,3,4,5};
double[] dy = {0,.9,4.5,8,18,24};
double[] x,y;
static CurveFitter cf;
static int fitType = -1;
static String equation = "y = a + b*x + c*x*x";
static final int USER_DEFINED = -1;
public Fitter() {
super("Curve Fitter");
WindowManager.addWindow(this);
addKeyListener(this);
Panel panel = new Panel();
fit = new Choice();
for (int i=0; i<CurveFitter.fitList.length; i++)
fit.addItem(CurveFitter.fitList[CurveFitter.sortedTypes[i]]);
fit.addItem("*User-defined*");
fit.addItemListener(this);
panel.add(fit);
doIt = new Button(" Fit ");
doIt.addActionListener(this);
doIt.addKeyListener(this);
panel.add(doIt);
open = new Button("Open");
open.addActionListener(this);
panel.add(open);
apply = new Button("Apply");
apply.addActionListener(this);
panel.add(apply);
settings = new Checkbox("Show settings", false);
panel.add(settings);
add("North", panel);
String text = "";
for (int i=0; i<dx.length; i++)
text += IJ.d2s(dx[i],2)+" "+IJ.d2s(dy[i],2)+"\n";
textArea = new TextArea("",15,30,TextArea.SCROLLBARS_VERTICAL_ONLY);
textArea.setFont(new Font("Monospaced", Font.PLAIN, 12));
if (IJ.isLinux()) textArea.setBackground(Color.white);
textArea.append(text);
add("Center", textArea);
pack();
GUI.center(this);
show();
IJ.register(Fitter.class);
}
public boolean doFit(int fitType) {
if (!getData()) {
IJ.beep();
return false;
}
cf = new CurveFitter(x, y);
cf.setStatusAndEsc("Optimization: Iteration ", true);
try {
if (fitType==USER_DEFINED) {
String eqn = getEquation();
if (eqn==null) return false;
int params = cf.doCustomFit(eqn, null, settings.getState());
if (params==0) {
IJ.beep();
IJ.log("Bad formula; should be:\n y = function(x, a, ...)");
return false;
}
} else
cf.doFit(fitType, settings.getState());
if (cf.getStatus() == Minimizer.INITIALIZATION_FAILURE) {
IJ.beep();
IJ.showStatus(cf.getStatusString());
IJ.log("Curve Fitting Error:\n"+cf.getStatusString());
return false;
}
if (Double.isNaN(cf.getSumResidualsSqr())) {
IJ.beep();
IJ.showStatus("Error: fit yields Not-a-Number");
return false;
}
} catch (Exception e) {
IJ.handleException(e);
return false;
}
IJ.log(cf.getResultString());
plot(cf);
this.fitType = fitType;
return true;
}
String getEquation() {
GenericDialog gd = new GenericDialog("Formula");
gd.addStringField("Formula:", equation, 38);
gd.showDialog();
if (gd.wasCanceled())
return null;
equation = gd.getNextString();
return equation;
}
public static void plot(CurveFitter cf) {
plot(cf, false);
}
public static void plot(CurveFitter cf, boolean eightBitCalibrationPlot) {
double[] x = cf.getXPoints();
double[] y = cf.getYPoints();
if (cf.getParams().length<cf.getNumParams()) {
Plot plot = new Plot(cf.getFormula(),"X","Y",x,y);
plot.setColor(Color.BLUE);
plot.addLabel(0.02, 0.1, cf.getName());
plot.addLabel(0.02, 0.2, cf.getStatusString());
plot.show();
return;
}
int npoints = 100;
if (npoints<x.length)
npoints = x.length; if (npoints>1000)
npoints = 1000;
double[] a = Tools.getMinMax(x);
double xmin=a[0], xmax=a[1];
if (eightBitCalibrationPlot) {
npoints = 256;
xmin = 0;
xmax = 255;
}
a = Tools.getMinMax(y);
double ymin=a[0], ymax=a[1]; float[] px = new float[npoints];
float[] py = new float[npoints];
double inc = (xmax-xmin)/(npoints-1);
double tmp = xmin;
for (int i=0; i<npoints; i++) {
px[i]=(float)tmp;
tmp += inc;
}
double[] params = cf.getParams();
for (int i=0; i<npoints; i++)
py[i] = (float)cf.f(params, px[i]);
a = Tools.getMinMax(py);
double dataRange = ymax - ymin;
ymin = Math.max(ymin - dataRange, Math.min(ymin, a[0])); ymax = Math.min(ymax + dataRange, Math.max(ymax, a[1]));
Plot plot = new Plot(cf.getFormula(),"X","Y",px,py);
plot.setLimits(xmin, xmax, ymin, ymax);
plot.setColor(Color.RED);
plot.addPoints(x, y, PlotWindow.CIRCLE);
plot.setColor(Color.BLUE);
StringBuffer legend = new StringBuffer(100);
legend.append(cf.getName()); legend.append('\n');
legend.append(cf.getFormula()); legend.append('\n');
double[] p = cf.getParams();
int n = cf.getNumParams();
char pChar = 'a';
for (int i = 0; i < n; i++) {
legend.append(pChar+" = "+IJ.d2s(p[i],5,9)+'\n');
pChar++;
}
legend.append("R^2 = "+IJ.d2s(cf.getRSquared(),4)); legend.append('\n');
plot.addLabel(0.02, 0.1, legend.toString());
plot.setColor(Color.BLUE);
plot.show();
}
double sqr(double x) {return x*x;}
boolean getData() {
textArea.selectAll();
String text = textArea.getText();
text = zapGremlins(text);
textArea.select(0,0);
StringTokenizer st = new StringTokenizer(text, " \t\n\r,");
int nTokens = st.countTokens();
if (nTokens<4 || (nTokens%2)!=0) {
IJ.showStatus("Data error: min. two (x,y) pairs needed");
return false;
}
int n = nTokens/2;
x = new double[n];
y = new double[n];
for (int i=0; i<n; i++) {
String xString = st.nextToken();
String yString = st.nextToken();
x[i] = Tools.parseDouble(xString);
y[i] = Tools.parseDouble(yString);
if (Double.isNaN(x[i]) || Double.isNaN(y[i])) {
IJ.showStatus("Data error: Bad number at "+i+": "+xString+" "+yString);
return false;
}
}
return true;
}
void applyFunction() {
if (cf==null || fitType < 0) {
IJ.error("No function available");
return;
}
ImagePlus img = WindowManager.getCurrentImage();
if (img==null) {
IJ.noImage();
return;
}
if (img.getTitle().matches("y\\s=.*")) { IJ.error("First select the image to be transformed");
return;
}
double[] p = cf.getParams();
int width = img.getWidth();
int height = img.getHeight();
int size = width*height;
float[] data = new float[size];
ImageProcessor ip = img.getProcessor();
float value;
for (int y=0; y<height; y++) {
for (int x=0; x<width; x++) {
value = ip.getPixelValue(x,y);
data[y*width+x] = (float)cf.f(p, value);
}
}
ImageProcessor ip2 = new FloatProcessor(width, height, data, ip.getColorModel());
new ImagePlus(img.getTitle()+"-transformed", ip2).show();
}
void open() {
OpenDialog od = new OpenDialog("Open Text File...", "");
String directory = od.getDirectory();
String name = od.getFileName();
if (name==null)
return;
String path = directory + name;
textArea.selectAll();
textArea.setText("");
try {
BufferedReader r = new BufferedReader(new FileReader(directory+name));
while (true) {
String s=r.readLine();
if (s==null ||(s.length()>100))
break;
textArea.append(s+"\n");
}
r.close();
}
catch (Exception e) {
IJ.error(e.getMessage());
return;
}
}
public void itemStateChanged(ItemEvent e) {
fitTypeStr = fit.getSelectedItem();
}
public void actionPerformed(ActionEvent e) {
if (e.getSource() instanceof MenuItem) {
String cmd = e.getActionCommand();
if (cmd==null) return;
if (cmd.equals("Cut"))
cut();
else if (cmd.equals("Copy"))
copy();
else if (cmd.equals("Paste"))
paste();
return;
}
try {
if (e.getSource()==doIt) {
final int fitType = CurveFitter.getFitCode(fit.getSelectedItem());
Thread thread = new Thread(
new Runnable() {
final public void run() {
doFit(fitType);
}
}, "CurveFitting"
);
thread.setPriority(Thread.currentThread().getPriority());
thread.start();
} else if (e.getSource()==apply)
applyFunction();
else
open();
} catch (Exception ex) {IJ.log(""+ex);}
}
String zapGremlins(String text) {
char[] chars = new char[text.length()];
chars = text.toCharArray();
int count=0;
for (int i=0; i<chars.length; i++) {
char c = chars[i];
if (c!='\n' && c!='\t' && (c<32||c>127)) {
count++;
chars[i] = ' ';
}
}
if (count>0)
return new String(chars);
else
return text;
}
public void keyTyped (KeyEvent e) {}
public void keyReleased (KeyEvent e) {}
public void keyPressed (KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ESCAPE)
IJ.getInstance().keyPressed(e);
}
private boolean copy() {
String s = textArea.getSelectedText();
Clipboard clip = getToolkit().getSystemClipboard();
if (clip!=null) {
StringSelection cont = new StringSelection(s);
clip.setContents(cont,this);
return true;
} else
return false;
}
private void cut() {
if (copy()) {
int start = textArea.getSelectionStart();
int end = textArea.getSelectionEnd();
textArea.replaceRange("", start, end);
}
}
private void paste() {
String s;
s = textArea.getSelectedText();
Clipboard clipboard = getToolkit( ). getSystemClipboard();
Transferable clipData = clipboard.getContents(s);
try {
s = (String)(clipData.getTransferData(DataFlavor.stringFlavor));
} catch (Exception e) {
s = e.toString( );
}
int start = textArea.getSelectionStart( );
int end = textArea.getSelectionEnd( );
textArea.replaceRange(s, start, end);
if (IJ.isMacOSX())
textArea.setCaretPosition(start+s.length());
}
public void lostOwnership (Clipboard clip, Transferable cont) {}
}