import ij.IJ; import ij.ImagePlus; import ij.gui.Line; import ij.gui.Roi; import ij.plugin.PlugIn; import ij.process.ImageProcessor; import java.awt.Container; import java.awt.FlowLayout; import java.awt.BorderLayout; import java.awt.event.ActionListener; import java.awt.event.ActionEvent; import javax.swing.JDialog; import javax.swing.JPanel; import javax.swing.JLabel; import javax.swing.JButton; /** * @author Tom Maddock */ public class EdgeFitter_ implements PlugIn, ActionListener { /* Version Information */ static final public String VERSION = "1.0"; /* This constant is used to eliminate tiny delta variations * along areas of roughly the same color (i.e. background). * The higher the value, the more delta values between pixels * will be disregarded from the edge computation. */ static final private double QuantConst = 1; /* This constant is used to enable or disable a bit of code * that tends to increase the boundaries of the detected * edge. It does this by first locating the point of maximum * change for each edge, then ignoring all the values on the * inner side of the maximum point. */ static final private boolean CutInnerHalf = false; /* This constant is used to enable or disable an extra bit of * edge detection code that emphasizes the most prominent part * of an edge, and places less emphasis on the smaller gradients. */ static final private boolean IsConservative = false; /* This constant is used to set the width of the line that is * used to sample pixel values from the image. The wider the * line, the larger the number of pixels that are sampled, which * results in a edge-fitting that is more characteristic of the * whole objective instead of just the part that the line sees. */ static final private int LineWidth = 2; /* Help dialog title for this plugin */ static final private String HelpTitle = "Help For EdgeFitter Plugin v" + VERSION; /* Help dialog message for this plugin */ static final private String HelpMessage = "" + "This plugin is used to fit a line so that its endpoints lie on the edges
" + "of an object in an image. The most direct application of this plugin is
" + "in measuring the width of objects. For example, if the user wished to
" + "measure the diameter of a circular object in an image, the user would simply
" + "draw an inexact line across the center of the object, then run this plugin.
" + "The line would be corrected so that its endpoints were exactly on the edges
" + "of the object, and thus its length would equal the width of the object.
" + "A pivotal feature of this plugin is that it offers a way of performing such
" + "measurements on objects within an image, while minimizing the amount of bias
" + "that the user may inadvertently introduce into the process. Since the plugin
" + "determines where the boundaries of the object are, the user cannot directly
" + "influence the final result. The user may only select the object to measure
" + "This plugin requires that the image be an 8-bit grayscale image, and also
" + "that any objects which are going to be fitted using this plugin are brighter
" + "than the background. If the objects are darker than the background, then the
" + "image simply needs to be inverted before the plugin is used." + ""; /* ----- Class Functions ----- */ /* This function expects to be given a line of pixels that is drawn across * a "hotspot" in the image, where there is both a rising and a falling edge. * It will give back two values indicating how far along that line of pixels * those edges are. */ public double[] DetectEdges(double[] pixels) { int i, maxpos; // Prepare the array to hold the edges double[] edges = new double[2]; // Search through for a maximum point for (i = 0, maxpos = 0; i < pixels.length; i++) if (pixels[i] > pixels[maxpos]) maxpos = i; // Detect the first edge double[] edge = new double[maxpos + 1]; for (i = 0; i < edge.length; i++) edge[i] = pixels[i]; edges[0] = FindEdge(edge, true); // Detect the second edge edge = new double[pixels.length - maxpos]; for (i = 0; i < edge.length; i++) edge[i] = pixels[maxpos + i]; edges[1] = FindEdge(edge, false) + maxpos; return edges; } /* This function expects to be given a line of pixels that is drawn across * an edge. It will give back the position along that line where it decides * the boundary of the edge actually is. */ public double FindEdge(double[] pixels, boolean risingedge) { int i; // Create an array for the delta values double[] deltas = new double[pixels.length - 1]; // Calculate the absolute difference between adjacent pixels for (i = 0; i < deltas.length; i++) deltas[i] = Math.abs(pixels[i+1] - pixels[i]); // Quantize the deltas to remove tiny variations for (i = 0; i < deltas.length; i++) if (deltas[i] < QuantConst) deltas[i] = 0; double sum = 0; // Find the sum of all the deltas for (i = 0; i < deltas.length; i++) sum += deltas[i]; // Run conservative estimate code if desired if (IsConservative) { // Find the average of the deltas double average = sum / deltas.length; // Scale deltas to emphasize those above the average for (i = 0; i < deltas.length; i++) deltas[i] = deltas[i]*(deltas[i]/average); // Recalculate the sum for (i = 0, sum = 0; i < deltas.length; i++) sum += deltas[i]; } if (CutInnerHalf) { int max = 0; // Find the point of maximum change for (i = 0; i < deltas.length; i++) if (deltas[i] >= deltas[max]) max = i; // Cut everything inside the maximum if (risingedge) for (i = max+1; i < deltas.length; i++) deltas[i] = 0; else for (i = 0; i < max; i++) deltas[i] = 0; // Recalculate the sum for (i = 0, sum = 0; i < deltas.length; i++) sum += deltas[i]; } double edgepos = 0; // Compute the weighted average for (i = 0; i < deltas.length; i++) edgepos += i * (deltas[i] / sum); // Adjust for delta offset and return return edgepos + .5; } /* Shows the help dialog box */ public void showHelp() { JDialog helpdialog = new JDialog(IJ.getInstance(), HelpTitle, false); // Set up the dialog's contents ij.util.Java2.setSystemLookAndFeel(); Container container = helpdialog.getContentPane(); container.setLayout(new BorderLayout()); JPanel panel = new JPanel(); panel.setLayout(new FlowLayout(FlowLayout.CENTER, 15, 15)); JLabel label = new JLabel(HelpMessage); panel.add(label); container.add(panel, "Center"); panel = new JPanel(); JButton button = new JButton("OK"); button.setActionCommand("CLOSEHELP"); button.addActionListener(this); button.putClientProperty("DIALOG", helpdialog); panel.add(button); container.add(panel, "South"); helpdialog.pack(); // Show the help dialog ij.gui.GUI.center(helpdialog); helpdialog.setVisible(true); } /* This function processes all custom button clicks*/ public void actionPerformed(ActionEvent e) { // The "OK" button on the help dialog was clicked if (e.getActionCommand().equals("CLOSEHELP")){ JDialog helpdialog = (JDialog)((JButton)e.getSource()).getClientProperty("DIALOG"); helpdialog.setVisible(false); helpdialog.dispose(); } } public void run(String arg0) { double begin = 0, end = 0, length = 0; String message; int i, oldwidth; // Should we display the help message? if (IJ.shiftKeyDown() || (arg0 != null && arg0.equalsIgnoreCase("HELP"))) { // Show the help message showHelp(); // Unset the shift key if it is set if (IJ.shiftKeyDown()) IJ.setKeyUp(java.awt.event.KeyEvent.VK_SHIFT); return; } // Get the current image ImagePlus image = IJ.getImage(); // Make sure the image is the right type if (image.getType() != ImagePlus.GRAY8) { IJ.showMessage("Error", "8-Bit Grayscale Image Required"); return; } // Get the current ROI Roi roi = image.getRoi(); // First check to make sure the ROI is correct if (roi == null || roi.getType() != Roi.LINE) { // Print error message and give up IJ.showMessage("Error", "Selection must be a straight line"); return; } // Set interpolation image.getProcessor().setInterpolate(true); // Cast the ROI into a line Line line = (Line)roi; // Get the coordinates of the line double y1 = line.y1; double y2 = line.y2; double x1 = line.x1; double x2 = line.x2; // Change the Line ROI width oldwidth = Line.getWidth(); Line.setWidth(LineWidth); // Get the pixels in the line double[] values = line.getPixels(); // Set the line width back Line.setWidth(oldwidth); // Find the edge double edgepos[] = DetectEdges(values); length = values.length; begin = edgepos[0]; end = edgepos[1]; // Adapt the line x1 = line.x1+((begin)/length)*(line.x2-line.x1); y1 = line.y1+((begin)/length)*(line.y2-line.y1); x2 = line.x1+((end)/length)*(line.x2-line.x1); y2 = line.y1+((end)/length)*(line.y2-line.y1); Line newROI = new Line( (int)Math.round(x1), (int)Math.round(y1), (int)Math.round(x2), (int)Math.round(y2)); // Set the new line image.setRoi(newROI); } }