import ij.*; import ij.plugin.*; import ij.process.*; import ij.gui.*; import ij.measure.Calibration; import java.awt.event.*; import java.util.EventListener; import java.awt.Rectangle; import java.awt.geom.*; import java.awt.*; import ij.measure.ResultsTable; import javax.swing.Timer; /** This plugin implements the KeyListener interface and listens for key events generated by the current image. */ public class Distance_Between_Polylines implements PlugIn, KeyListener, ActionListener { private static ResultsTable results = new ResultsTable(); ControlWindow window; ImagePlus img; int action; int x0, y0; int num_points; double x_step, y_step; double dx, dy; PolygonRoi polyline1, polyline2; public void run(String args) { this.img = WindowManager.getCurrentImage(); if ( this.img == null ){ IJ.noImage(); return; } ImageWindow win = this.img.getWindow(); ImageCanvas canvas = win.getCanvas(); EventListener[] listeners = canvas.getListeners(KeyListener.class); // kan bruke getKeyListeners for ( int i=0; i < listeners.length; ++i ){ if ( listeners[i].getClass() == this.getClass() ){ IJ.error("Distance_Between_Polylines already running for this window"); return; } } canvas.addKeyListener(this); window = new ControlWindow("Plugin Message Window", "Please draw a direction line and press [Enter]", this); this.action = 0; doNextAction(); } // Methods for handling key presses public void keyPressed(KeyEvent e) { doNextAction(); } public void keyReleased(KeyEvent e) {} public void keyTyped(KeyEvent e) {} // Methods for managing the window: public void actionPerformed( ActionEvent e ) { window.setVisible( false ); window.dispose(); this.img.getWindow().getCanvas().removeKeyListener(this); } void terminatePlugin() { Timer timeout = new Timer(2000, this); timeout.setRepeats(false); timeout.start(); // execute actionPerformed() in 2 seconds } // The important methods: void doNextAction(){ switch (action) { case 0: window.setMessage("Please draw a direction line and press [Enter]"); break; case 1: if ( ! readRefLine() ){ return; } window.setMessage("Draw the first line and press [Enter]"); break; case 2: if ( ! readFirstLine() ){ return; } window.setMessage("Draw the second line and press [Enter]"); break; case 3: if ( ! readSecondLine() ){ return; } if ( window.isShowing() ){ // another small hack doCalculations(); } terminatePlugin(); // also removes the KeyListener break; default: window.setMessage("Another time in action switch (reset counter?)"); break; } ++this.action; } boolean readRefLine(){ Roi roi = this.img.getRoi(); if ( roi != null && roi.getType() == Roi.POLYLINE ){ PolygonRoi polyline = (PolygonRoi) roi; if ( polyline.getNCoordinates() != 2 ){ IJ.error("The direction line must have only two points"); return false; } else { int[] x_coords = polyline.getXCoordinates(); int[] y_coords = polyline.getYCoordinates(); Rectangle offset = polyline.getBounds(); roi = (Roi) new Line(x_coords[0]+offset.x, y_coords[0]+offset.y, x_coords[1]+offset.x, y_coords[1]+offset.y); } } else if ( roi == null || roi.getType() != Roi.LINE ){ IJ.error("The direction line must be a line"); return false; } Line line = (Line) roi; this.dx = line.x2 - line.x1; this.dy = line.y2 - line.y1; double length = Math.sqrt( dx*dx + dy*dy ); this.num_points = (int) Math.floor( length ) + 1; this.x_step = dx / (num_points - 1); this.y_step = dy / (num_points - 1); this.x0 = line.x1; this.y0 = line.y1; return true; } boolean readFirstLine(){ Roi roi = this.img.getRoi(); if ( roi != null && roi.getType() == Roi.LINE ){ Line line = (Line) roi; int[] x_coords = { line.x1, line.x2 }; int[] y_coords = { line.y1, line.y2 }; roi = (Roi) new PolygonRoi( x_coords, y_coords, 2, Roi.POLYLINE ); } else if ( roi == null || !(roi.getType() == Roi.POLYLINE || roi.getType() == Roi.FREELINE) ){ IJ.error("This plugin only work with polylines"); return false; } this.polyline1 = (PolygonRoi) roi; return true; } boolean readSecondLine(){ Roi roi = this.img.getRoi(); if ( roi != null && roi.getType() == Roi.LINE ){ Line line = (Line) roi; int[] x_coords = { line.x1, line.x2 }; int[] y_coords = { line.y1, line.y2 }; roi = (Roi) new PolygonRoi( x_coords, y_coords, 2, Roi.POLYLINE ); } else if ( roi == null || !(roi.getType() == Roi.POLYLINE || roi.getType() == Roi.FREELINE) ){ IJ.error("This plugin only work with polylines"); return false; } this.polyline2 = (PolygonRoi) roi; return true; } double scaleX( double x ) { Calibration calib = this.img.getCalibration(); return (x-calib.xOrigin)*calib.pixelWidth; } double scaleY( double y ) { Calibration calib = this.img.getCalibration(); return (y-calib.yOrigin)*calib.pixelHeight; } int sign( double num ) { return (num < 0) ? -1 : 1; } void doCalculations(){ double x, y; double l1_x, l1_y; double l2_x, l2_y; double x_diff, y_diff; double[] distances = new double[num_points]; int num_distances = 0; double avg_distance = 0; double dist_variance = 0; /* // debugging: IJ.write("refline: " + x0 + "," + y0 + " -> " + (x0+dx) + "," + (y0+dy)); IJ.write(""); int[] x_coords, y_coords; Rectangle offset; IJ.write("polyline1: " + polyline1.getNCoordinates() + " coordinates"); x_coords = polyline1.getXCoordinates(); y_coords = polyline1.getYCoordinates(); offset = polyline1.getBounds(); for ( int i = 0; i < polyline1.getNCoordinates(); ++i ){ IJ.write("polyline1["+i+"] = " + (x_coords[i]+offset.x) + "," + (y_coords[i]+offset.y)); } IJ.write(""); IJ.write("polyline2: " + polyline2.getNCoordinates() + " coordinates"); x_coords = polyline2.getXCoordinates(); y_coords = polyline2.getYCoordinates(); offset = polyline2.getBounds(); for ( int i = 0; i < polyline2.getNCoordinates(); ++i ){ IJ.write("polyline2["+i+"] = " + (x_coords[i]+offset.x) + "," + (y_coords[i]+offset.y)); } IJ.write(""); // end debugging */ for ( int point = 0; point < num_points; ++point ){ // the actual point on the reference/direction line x = scaleX(this.x0 + point*this.x_step); y = scaleY(this.y0 + point*this.y_step); Point2D.Double p1 = getOrthogonalPoint( x, y, polyline1 ); l1_x = p1.getX(); l1_y = p1.getY(); if ( l1_x == 0 && l1_y == 0 ){ // et lite hack... continue; } Point2D.Double p2 = getOrthogonalPoint( x, y, polyline2 ); l2_x = p2.getX(); l2_y = p2.getY(); if ( l2_x == 0 && l2_y == 0 ){ // et lite hack her ogs�... continue; } // calculate the distance between the lines x_diff = l2_x - l1_x; y_diff = l2_y - l1_y; // store the distance in an array distances[num_distances] = Math.sqrt( x_diff*x_diff + y_diff*y_diff ); ++num_distances; if ( point % 10 == 0 ) { Calibration calib = this.img.getCalibration(); img.getProcessor().drawLine( (int)(l1_x/calib.pixelWidth + calib.xOrigin), (int)(l1_y/calib.pixelHeight + calib.yOrigin), (int)(l2_x/calib.pixelWidth + calib.xOrigin), (int)(l2_y/calib.pixelHeight + calib.yOrigin)); img.updateAndRepaintWindow(); } } // calculate average distance for ( int i = 0; i < num_distances; ++i ) { avg_distance += distances[i] / num_distances; } window.setMessage("Average distance: " + avg_distance); // calculate standard deviation (variance first) for ( int i = 0; i < num_distances; ++i ) { dist_variance += (distances[i] - avg_distance) * (distances[i] - avg_distance) / (num_distances - 1); } results.incrementCounter(); results.addLabel("Filename", this.img.getTitle()); results.addValue("Avg distance", avg_distance); results.addValue("Std deviation", Math.sqrt(dist_variance)); results.show("Average distances"); } public Point2D.Double getOrthogonalPoint( double x, double y, PolygonRoi polyline ) { // first find the approximately orthogonal point int[] x_coords = polyline.getXCoordinates(); int[] y_coords = polyline.getYCoordinates(); Rectangle offset = polyline.getBounds(); int i, lo_i, hi_i; double val, lo_val, hi_val; lo_i = 0; hi_i = polyline.getNCoordinates() - 1; lo_val = this.dx*(x-scaleX(x_coords[lo_i]+offset.x)) + this.dy*(y-scaleY(y_coords[lo_i]+offset.y)); hi_val = this.dx*(x-scaleX(x_coords[hi_i]+offset.x)) + this.dy*(y-scaleY(y_coords[hi_i]+offset.y)); if ( sign(lo_val) == sign(hi_val) ){ return new Point2D.Double(); // 0,0 indikerer feil :-} } for ( int diff = (hi_i-lo_i)/2; diff >= 1; diff = (hi_i-lo_i)/2 ){ i = lo_i + diff; val = this.dx*(x-scaleX(x_coords[i]+offset.x)) + this.dy*(y-scaleY(y_coords[i]+offset.y)); if ( sign(val) == sign(lo_val) ){ lo_i = i; lo_val = val; } else { hi_i = i; hi_val = val; } } // debuggin: if ( hi_i - lo_i != 1 ){ IJ.error("hi_i - lo_i != 1"); } // then find the exact orthogonal point double lo_x = scaleX(x_coords[lo_i]+offset.x); double lo_y = scaleY(y_coords[lo_i]+offset.y); double hi_x = scaleX(x_coords[hi_i]+offset.x); double hi_y = scaleY(y_coords[hi_i]+offset.y); double a1; // slope for the polyline between lo_x,lo_y and hi_x,hi_y if ( lo_x == hi_x ){ // vertical line a1 = 1e14; // use a really large number } else { a1 = ( hi_y - lo_y )/( hi_x - lo_x ); } // debugging: if ( Math.abs(a1) > 1e14 ){ IJ.error("a1 is a really big number: "+a1); } double a2; // slope for the orthogonal to the direction line at x,y if ( this.dy != 0 ){ a2 = -( this.dx/this.dy ); // the orthogonal line has coordinates (x,y)+a2*(-dy,dx) } else { // direction line horizontal, and thus the orthogonal is vertical //a2 = 1e14; // use a really large number return new Point2D.Double( x, lo_y + a1*(x-lo_x) ); } // debugging: if ( Math.abs(a2) > 1e14 ){ IJ.error("a2 is a really big number: "+a2); } if ( a1 == a2 ){ // do I need some fuzziness here? // the line segment between lo_x,lo_y and hi_x,hi_y is // perpendicular to the direction line. Just use the middle point. return new Point2D.Double( (hi_x+lo_x)/2, (hi_y+lo_y)/2 ); } else { double xn = ( (y-a2*x) - (lo_y-a1*lo_x) )/(a1-a2); return new Point2D.Double( xn, y+a2*(xn-x) ); } } } class ControlWindow extends Dialog { Label label; public ControlWindow(String title, String message, ActionListener listener) { super( IJ.getInstance(), title, false ); setLayout( new BorderLayout() ); if ( message==null ){ message = ""; } Panel center = new Panel(); center.setLayout( new FlowLayout( FlowLayout.CENTER, 15, 15 ) ); add( "Center", center ); this.label = new Label(); center.add( this.label ); setMessage( message ); Button button = new Button( " End plugin " ); button.addActionListener( listener ); Panel panel = new Panel(); panel.setLayout( new FlowLayout() ); panel.add( button ); add( "South", panel ); if ( ij.IJ.isMacintosh() ){ // setResizable( false ); } pack(); placeUpperRight( this ); show(); } public void setMessage( String new_message ) { label.setText( new_message ); if ( label.getMinimumSize().getWidth() > label.getSize().getWidth() ){ // how do I make the label and window larger? this.validate(); } } static void placeUpperRight(Window win) { Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); Dimension window = win.getSize(); if (window.width==0){ return; } win.setLocation( screen.width-window.width, 0 ); } }