import java.awt.*; import java.awt.event.*; import java.awt.SystemColor; import java.io.*; import java.lang.*; import ij.plugin.frame.*; import ij.*; import ij.util.*; import ij.process.*; import ij.gui.*; /** * This plugin simplifies the task of measuring the Modulation Transfer * Function (resolution) of an optical system, given an image of variously * sized bar patterns.
* This dialog-box-like plugin is based on the PlugInFrame class, * and could serve as an example of how to automate repeated * measurements.
* * Reference:
* Sitter, D.N., Goddard, J.S., and Ferrell, R.K., (1995), "Method for the
* measurement of the modulation transfer function of sampled imaging systems
* from bar-target patterns.", Applied Optics, v. 34 n. 4, pp. 746-751.
*
* @author Jeffrey Kuhn
* @author The University of Texas at Austin
* @author jkuhn@ccwf.cc.utexas.edu
*/
public class MeasureMTF_ extends PlugInFrame implements ActionListener {
/*
* User interface fields
*/
Panel panel;
TextField textMeasuredLength;
Button buttonRetrieveLength;
TextField textKnownLength;
Label labelScale;
Label labelWhiteLevel;
Button buttonMeasureWhite;
Label labelBlackLevel;
Button buttonMeasureBlack;
TextField textCyclesToMeasure;
Choice choiceBarSpacing;
TextField textDutyCycle;
TextField textLinesToAverage;
Label labelMeasurementSize;
Checkbox checkVertical;
Checkbox checkFirstTopLeft;
Checkbox checkPlotDFT;
Button buttonRecalculate;
Button buttonCreateRoi;
Button buttonMeasureMtf;
Button buttonClearMtf;
Button buttonFirstBar;
Button buttonNextBar;
/*
* calculation fields
*/
/** number of DFT harmonics to plot */
static final int nHARMONICS = 15;
/** distance between each grating in um (micrometers) */
static final double dBARLENGTH = 5;
/** measured length in pixels */
static double dMeasuredLength = 0;
/** known length in um (micrometers) */
double dKnownLength = 20;
/** calculated scale in pix/um */
double dScale = 0;
/** measured white bar intensity */
double dWhiteLevel = -1;
/** measured black bar intensity */
double dBlackLevel = -1;
/** number of cycles to measure */
int iCyclesToMeasure = 5;
/** distance between cycles */
double dBarSpacing;
/** length of white portion/total spacing
* (i.e. if white=1/4 width and black=3/4 width, Duty Cycle=0.25) */
double dDutyCycle = 0.5;
/** how many lines of pixel data to average for each bar profile */
int iLinesToAverage = 50;
/** total length of profile to measure */
int iMeasurementSize = 0;
/** measure vertically? */
boolean bVertical = false;
/** first bar is at the top or on the left */
boolean bFirstTopLeft = true;
/** plot the DFT result? */
boolean bPlotDFT = true;
/** Have the column headings in the main measurement window been created? */
static boolean bMtfHeadingsCreated = false;
static boolean bHasMtfData = false;
/**
* Constructor. Creates the main user interface panel.
*/
public MeasureMTF_() {
super("Measure MTF");
setBackground(SystemColor.control);
setLayout(new BorderLayout());
panel = new Panel();
panel.setLayout(new GridLayout(14, 3, 2, 2));
panel.setBackground(SystemColor.control);
//-------------------NEXT ROW-----------------------
panel.add(new Label("Measured Length:"));
textMeasuredLength = new TextField(Double.toString(dMeasuredLength));
panel.add(textMeasuredLength);
buttonRetrieveLength = new Button("Retrieve");
buttonRetrieveLength.addActionListener(this);
panel.add(buttonRetrieveLength);
//-------------------NEXT ROW-----------------------
panel.add(new Label("Known Length:"));
textKnownLength = new TextField(Double.toString(dKnownLength));
panel.add(textKnownLength);
panel.add(new Label("um"));
//-------------------NEXT ROW-----------------------
panel.add(new Label("Scale:"));
labelScale = new Label("unknown");
panel.add(labelScale);
panel.add(new Label("pix/um"));
//-------------------NEXT ROW-----------------------
panel.add(new Label("White Level:"));
labelWhiteLevel = new Label("unknown");
panel.add(labelWhiteLevel);
buttonMeasureWhite = new Button("Measure White");
buttonMeasureWhite.addActionListener(this);
panel.add(buttonMeasureWhite);
//-------------------NEXT ROW-----------------------
panel.add(new Label("Black Level:"));
labelBlackLevel = new Label("unknown");
panel.add(labelBlackLevel);
buttonMeasureBlack = new Button("Measure Black");
buttonMeasureBlack.addActionListener(this);
panel.add(buttonMeasureBlack);
//-------------------NEXT ROW-----------------------
panel.add(new Label("Cycles to Measure:"));
textCyclesToMeasure = new TextField(Integer.toString(iCyclesToMeasure));
panel.add(textCyclesToMeasure);
panel.add(new Label());
//-------------------NEXT ROW-----------------------
panel.add(new Label("Bar Spacing:"));
choiceBarSpacing = new Choice();
choiceBarSpacing.add("4.000");
choiceBarSpacing.add("2.000");
choiceBarSpacing.add("1.000");
choiceBarSpacing.add("0.500");
choiceBarSpacing.add("0.250");
choiceBarSpacing.add("0.200");
panel.add(choiceBarSpacing);
panel.add(new Label("um"));
//-------------------NEXT ROW-----------------------
panel.add(new Label("Duty Cycle:"));
textDutyCycle = new TextField(Double.toString(dDutyCycle));
panel.add(textDutyCycle);
panel.add(new Label("white width/spacing"));
//-------------------NEXT ROW-----------------------
panel.add(new Label("Lines To Average:"));
textLinesToAverage = new TextField(Integer.toString(iLinesToAverage));
panel.add(textLinesToAverage);
panel.add(new Label());
//-------------------NEXT ROW-----------------------
panel.add(new Label("Measurement Size:"));
labelMeasurementSize = new Label("unknown");
panel.add(labelMeasurementSize);
panel.add(new Label("pix"));
//-------------------NEXT ROW-----------------------
panel.add(new Label());
panel.add(new Label());
panel.add(new Label());
//-------------------NEXT ROW-----------------------
checkVertical = new Checkbox("Vertical Bars", bVertical);
panel.add(checkVertical);
buttonCreateRoi = new Button("Create ROI");
buttonCreateRoi.addActionListener(this);
panel.add(buttonCreateRoi);
buttonMeasureMtf = new Button("Measure MTF");
buttonMeasureMtf.addActionListener(this);
panel.add(buttonMeasureMtf);
//-------------------NEXT ROW-----------------------
checkFirstTopLeft = new Checkbox("First at top/left", bFirstTopLeft);
panel.add(checkFirstTopLeft);
buttonFirstBar = new Button("Goto First Bar");
buttonFirstBar.addActionListener(this);
panel.add(buttonFirstBar);
buttonClearMtf = new Button("Clear MTF");
buttonClearMtf.addActionListener(this);
panel.add(buttonClearMtf);
//-------------------NEXT ROW-----------------------
checkPlotDFT = new Checkbox("Plot DFT", bPlotDFT);
panel.add(checkPlotDFT);
buttonNextBar = new Button("Goto Next Bar");
buttonNextBar.addActionListener(this);
panel.add(buttonNextBar);
buttonRecalculate = new Button("Update Values");
buttonRecalculate.addActionListener(this);
panel.add(buttonRecalculate);
add(panel, BorderLayout.CENTER);
pack();
show();
}
/**
* Handle button presses, etc.
*/
public void actionPerformed(ActionEvent e) {
if (e.getSource() == buttonRecalculate) {
recalculate();
return;
}
if (e.getSource() == buttonClearMtf) {
if (bHasMtfData) {
// ask the user if they really want to erase the data
boolean okay = IJ.showMessageWithCancel(
"Clear MTF",
"There is MTF data in the measurement window.\n" +
"are you sure that you want to clear it?.");
if (okay) {
clearMTF();
}
} else {
clearMTF();
}
return;
}
// Everything from here, down requires an image to work with
ImagePlus imp = WindowManager.getCurrentImage();
if (imp == null) {
IJ.error("There is no active image.");
return;
}
if (e.getSource() == buttonRetrieveLength) {
bVertical = checkVertical.getState();
Rectangle r = imp.getProcessor().getRoi();
if (bVertical) {
dMeasuredLength = r.height;
} else {
dMeasuredLength = r.width;
}
textMeasuredLength.setText(Double.toString(dMeasuredLength));
recalculate();
return;
}
if (e.getSource() == buttonMeasureWhite) {
ImageStatistics stat = imp.getStatistics();
dWhiteLevel = stat.mean;
int iInt = (int)dWhiteLevel;
int iFrac = (int)((dWhiteLevel - iInt)*10);
labelWhiteLevel.setText(Integer.toString(iInt)+"."+Integer.toString(iFrac));
recalculate();
return;
}
if (e.getSource() == buttonMeasureBlack) {
ImageStatistics stat = imp.getStatistics();
dBlackLevel = stat.mean;
int iInt = (int)dBlackLevel;
int iFrac = (int)((dBlackLevel - iInt)*10);
labelBlackLevel.setText(Integer.toString(iInt)+"."+Integer.toString(iFrac));
recalculate();
return;
}
if (e.getSource() == buttonCreateRoi) {
recalculate();
createROI(imp);
return;
}
if (e.getSource() == buttonFirstBar) {
recalculate();
int iCurrentBar = choiceBarSpacing.getSelectedIndex();
double dDistDown = -iCurrentBar * dBARLENGTH;
double dDistOver = 0;
// Calculate how far we have to move back over
for (int i=0; i