package ij.gui; import ij.*; import ij.process.*; import ij.measure.*; import ij.plugin.frame.*; import java.awt.*; import java.awt.image.*; import java.awt.geom.*; /** This class represents a polygon region of interest or polyline of interest. */ public class PolygonRoi extends Roi { protected int maxPoints = 1000; // will be increased if necessary protected int[] xp, yp; // image coordinates relative to origin of roi bounding box protected float[] xpf, ypf; // or alternative sub-pixel coordinates protected int[] xp2, yp2; // absolute screen coordinates protected int nPoints; protected float[] xSpline,ySpline; // relative image coordinates protected int splinePoints = 200; Rectangle clip; private double angle1, degrees=Double.NaN; private int xClipMin, yClipMin, xClipMax, yClipMax; private boolean userCreated; long mouseUpTime = 0; /** Creates a new polygon or polyline ROI from x and y coordinate arrays. Type must be Roi.POLYGON, Roi.FREEROI, Roi.TRACED_ROI, Roi.POLYLINE, Roi.FREELINE or Roi.ANGLE.*/ public PolygonRoi(int[] xPoints, int[] yPoints, int nPoints, int type) { super(0, 0, null); init1(nPoints, type); xp = xPoints; yp = yPoints; if (type!=TRACED_ROI) { xp = new int[nPoints]; yp = new int[nPoints]; for (int i=0; i<nPoints; i++) { xp[i] = xPoints[i]; yp[i] = yPoints[i]; } } xp2 = new int[nPoints]; yp2 = new int[nPoints]; init2(type); } /** Creates a new polygon or polyline ROI from x and y float arrays. Type must be Roi.POLYGON, Roi.FREEROI, Roi.POLYLINE, Roi.FREELINE or Roi.ANGLE.*/ public PolygonRoi(float[] xPoints, float[] yPoints, int nPoints, int type) { super(0, 0, null); init1(nPoints, type); xpf = xPoints; ypf = yPoints; xp2 = new int[nPoints]; yp2 = new int[nPoints]; init2(type); } private void init1(int nPoints, int type) throws IllegalArgumentException{ maxPoints = nPoints; this.nPoints = nPoints; if (type==POLYGON) this.type = POLYGON; else if (type==FREEROI) this.type = FREEROI; else if (type==TRACED_ROI) this.type = TRACED_ROI; else if (type==POLYLINE) this.type = POLYLINE; else if (type==FREELINE) this.type = FREELINE; else if (type==ANGLE) this.type = ANGLE; else if (type==POINT) this.type = POINT; else throw new IllegalArgumentException("Invalid type"); } private void init2(int type) { if (type==ANGLE && nPoints==3) getAngleAsString(); if (type==POINT && Toolbar.getMultiPointMode()) { Prefs.pointAutoMeasure = false; Prefs.pointAutoNextSlice = false; Prefs.pointAddToManager = false; userCreated = true; } if (lineWidth>1 && isLine()) updateWideLine(lineWidth); finishPolygon(); } /** Creates a new polygon or polyline ROI from a Polygon. Type must be Roi.POLYGON, Roi.FREEROI, Roi.TRACED_ROI, Roi.POLYLINE, Roi.FREELINE or Roi.ANGLE.*/ public PolygonRoi(Polygon p, int type) { this(p.xpoints, p.ypoints, p.npoints, type); } /** @deprecated */ public PolygonRoi(int[] xPoints, int[] yPoints, int nPoints, ImagePlus imp, int type) { this(xPoints, yPoints, nPoints, type); setImage(imp); } /** Starts the process of creating a new user-generated polygon or polyline ROI. */ public PolygonRoi(int sx, int sy, ImagePlus imp) { super(sx, sy, imp); int tool = Toolbar.getToolId(); switch (tool) { case Toolbar.POLYGON: type=POLYGON; break; case Toolbar.FREEROI: type=FREEROI; break; case Toolbar.FREELINE: type=FREELINE; break; case Toolbar.ANGLE: type=ANGLE; break; default: type = POLYLINE; break; } if (this instanceof EllipseRoi) { xpf = new float[maxPoints]; ypf = new float[maxPoints]; } else { xp = new int[maxPoints]; yp = new int[maxPoints]; } xp2 = new int[maxPoints]; yp2 = new int[maxPoints]; nPoints = 2; x = ic.offScreenX(sx); y = ic.offScreenY(sy); width=1; height=1; clipX = x; clipY = y; clipWidth = 1; clipHeight = 1; state = CONSTRUCTING; userCreated = true; if (lineWidth>1 && isLine()) updateWideLine(lineWidth); } private void drawStartBox(Graphics g) { if (type!=ANGLE) g.drawRect(ic.screenX(startX)-4, ic.screenY(startY)-4, 8, 8); } public void draw(Graphics g) { updatePolygon(); Color color = strokeColor!=null? strokeColor:ROIColor; boolean fill = false; if (fillColor!=null && !isLine() && state!=CONSTRUCTING) { color = fillColor; fill = true; } g.setColor(color); Graphics2D g2d = (Graphics2D)g; if (stroke!=null) g2d.setStroke(getScaledStroke()); if (xSpline!=null) { if (type==POLYLINE || type==FREELINE) { drawSpline(g, xSpline, ySpline, splinePoints, false, fill); if (wideLine && !overlay) { g2d.setStroke(onePixelWide); g.setColor(getColor()); drawSpline(g, xSpline, ySpline, splinePoints, false, fill); } } else drawSpline(g, xSpline, ySpline, splinePoints, true, fill); } else { if (type==POLYLINE || type==FREELINE || type==ANGLE || state==CONSTRUCTING) { g.drawPolyline(xp2, yp2, nPoints); if (wideLine && !overlay) { g2d.setStroke(onePixelWide); g.setColor(getColor()); g.drawPolyline(xp2, yp2, nPoints); } } else { if (fill) g.fillPolygon(xp2, yp2, nPoints); else g.drawPolygon(xp2, yp2, nPoints); } if (state==CONSTRUCTING && type!=FREEROI && type!=FREELINE) drawStartBox(g); } if ((xSpline!=null||type==POLYGON||type==POLYLINE||type==ANGLE) && state!=CONSTRUCTING && clipboard==null && !overlay) { mag = getMagnification(); int size2 = HANDLE_SIZE/2; if (activeHandle>0) drawHandle(g, xp2[activeHandle-1]-size2, yp2[activeHandle-1]-size2); if (activeHandle<nPoints-1) drawHandle(g, xp2[activeHandle+1]-size2, yp2[activeHandle+1]-size2); handleColor= strokeColor!=null? strokeColor:ROIColor; drawHandle(g, xp2[0]-size2, yp2[0]-size2); handleColor=Color.white; for (int i=1; i<nPoints; i++) drawHandle(g, xp2[i]-size2, yp2[i]-size2); } drawPreviousRoi(g); if (!(state==MOVING_HANDLE||state==CONSTRUCTING||state==NORMAL)) showStatus(); if (updateFullWindow) {updateFullWindow = false; imp.draw();} } private void drawSpline(Graphics g, float[] xpoints, float[] ypoints, int npoints, boolean closed, boolean fill) { float srcx=0f, srcy=0f, mag=1f; if (ic!=null) { Rectangle srcRect = ic.getSrcRect(); srcx=srcRect.x; srcy=srcRect.y; mag = (float)ic.getMagnification(); } float xf=x, yf=y; Graphics2D g2d = (Graphics2D)g; GeneralPath path = new GeneralPath(); if (mag==1f && srcx==0f && srcy==0f) { path.moveTo(xpoints[0]+xf, ypoints[0]+yf); for (int i=1; i<npoints; i++) path.lineTo(xpoints[i]+xf, ypoints[i]+yf); } else { path.moveTo((xpoints[0]-srcx+xf)*mag, (ypoints[0]-srcy+yf)*mag); for (int i=1; i<npoints; i++) path.lineTo((xpoints[i]-srcx+xf)*mag, (ypoints[i]-srcy+yf)*mag); } if (closed) path.lineTo((xpoints[0]-srcx+xf)*mag, (ypoints[0]-srcy+yf)*mag); if (fill) g2d.fill(path); else g2d.draw(path); } public void drawPixels(ImageProcessor ip) { int saveWidth = ip.getLineWidth(); if (getStrokeWidth()>1f) ip.setLineWidth((int)Math.round(getStrokeWidth())); if (xSpline!=null) { ip.moveTo(x+(int)(Math.floor(xSpline[0])+0.5), y+(int)Math.floor(ySpline[0]+0.5)); for (int i=1; i<splinePoints; i++) ip.lineTo(x+(int)(Math.floor(xSpline[i])+0.5), y+(int)Math.floor(ySpline[i]+0.5)); if (type==POLYGON || type==FREEROI || type==TRACED_ROI) ip.lineTo(x+(int)(Math.floor(xSpline[0])+0.5), y+(int)Math.floor(ySpline[0]+0.5)); } else if (xpf!=null) { ip.moveTo(x+(int)(Math.floor(xpf[0])+0.5), y+(int)Math.floor(ypf[0]+0.5)); for (int i=1; i<nPoints; i++) ip.lineTo(x+(int)(Math.floor(xpf[i])+0.5), y+(int)Math.floor(ypf[i]+0.5)); if (type==POLYGON || type==FREEROI || type==TRACED_ROI) ip.lineTo(x+(int)(Math.floor(xpf[0])+0.5), y+(int)Math.floor(ypf[0]+0.5)); } else { ip.moveTo(x+xp[0], y+yp[0]); for (int i=1; i<nPoints; i++) ip.lineTo(x+xp[i], y+yp[i]); if (type==POLYGON || type==FREEROI || type==TRACED_ROI) ip.lineTo(x+xp[0], y+yp[0]); } ip.setLineWidth(saveWidth); updateFullWindow = true; } protected void grow(int sx, int sy) { // Overrides grow() in Roi class } protected void updatePolygon() { int basex=0, basey=0; if (ic!=null) { Rectangle srcRect = ic.getSrcRect(); basex=srcRect.x; basey=srcRect.y; } if (getMagnification()==1.0 && basex==0 && basey==0) { if (xpf!=null) { for (int i=0; i<nPoints; i++) { xp2[i] = (int)(xpf[i]+x); yp2[i] = (int)(ypf[i]+y); } } else { for (int i=0; i<nPoints; i++) { xp2[i] = xp[i]+x; yp2[i] = yp[i]+y; } } } else { if (xpf!=null) { for (int i=0; i<nPoints; i++) { xp2[i] = ic.screenXD(xpf[i]+x); yp2[i] = ic.screenYD(ypf[i]+y); } } else { for (int i=0; i<nPoints; i++) { xp2[i] = ic.screenX(xp[i]+x); yp2[i] = ic.screenY(yp[i]+y); } } } } void handleMouseMove(int ox, int oy) { // Do rubber banding int tool = Toolbar.getToolId(); if (!(tool==Toolbar.POLYGON || tool==Toolbar.POLYLINE || tool==Toolbar.ANGLE)) { imp.killRoi(); imp.draw(); return; } drawRubberBand(ox, oy); degrees = Double.NaN; double len = -1; if (nPoints>1) { int x1 = xp[nPoints-2]; int y1 = yp[nPoints-2]; int x2 = xp[nPoints-1]; int y2 = yp[nPoints-1]; degrees = getAngle(x1, y1, x2, y2); if (tool!=Toolbar.ANGLE) { Calibration cal = imp.getCalibration(); double pw=cal.pixelWidth, ph=cal.pixelHeight; if (IJ.altKeyDown()) {pw=1.0; ph=1.0;} len = Math.sqrt((x2-x1)*pw*(x2-x1)*pw + (y2-y1)*ph*(y2-y1)*ph); } } if (tool==Toolbar.ANGLE) { if (nPoints==2) angle1 = degrees; else if (nPoints==3) { double angle2 = getAngle(xp[1], yp[1], xp[2], yp[2]); degrees = Math.abs(180-Math.abs(angle1-angle2)); if (degrees>180.0) degrees = 360.0-degrees; } } String length = len!=-1?", length=" + IJ.d2s(len):""; double degrees2 = tool==Toolbar.ANGLE&&nPoints==3&&Prefs.reflexAngle?360.0-degrees:degrees; String angle = !Double.isNaN(degrees)?", angle=" + IJ.d2s(degrees2):""; IJ.showStatus(imp.getLocationAsString(ox,oy) + length + angle); } void drawRubberBand(int ox, int oy) { int x1 = xp[nPoints-2]+x; int y1 = yp[nPoints-2]+y; int x2 = xp[nPoints-1]+x; int y2 = yp[nPoints-1]+y; int xmin=9999, ymin=9999, xmax=0, ymax=0; if (x1<xmin) xmin=x1; if (x2<xmin) xmin=x2; if (ox<xmin) xmin=ox; if (x1>xmax) xmax=x1; if (x2>xmax) xmax=x2; if (ox>xmax) xmax=ox; if (y1<ymin) ymin=y1; if (y2<ymin) ymin=y2; if (oy<ymin) ymin=oy; if (y1>ymax) ymax=y1; if (y2>ymax) ymax=y2; if (oy>ymax) ymax=oy; //clip = new Rectangle(xmin, ymin, xmax-xmin, ymax-ymin); int margin = 4; if (ic!=null) { double mag = ic.getMagnification(); if (mag<1.0) margin = (int)(margin/mag); } margin = (int)(margin+getStrokeWidth()); xp[nPoints-1] = ox-x; yp[nPoints-1] = oy-y; imp.draw(xmin-margin, ymin-margin, (xmax-xmin)+margin*2, (ymax-ymin)+margin*2); } void finishPolygon() { Rectangle r; if (xpf!=null) { FloatPolygon poly = new FloatPolygon(xpf, ypf, nPoints); r = poly.getBounds(); } else { Polygon poly = new Polygon(xp, yp, nPoints); r = poly.getBounds(); } x = r.x; y = r.y; width = r.width; height = r.height; if (xpf!=null) { for (int i=0; i<nPoints; i++) { xpf[i] = (float)(xpf[i]-x); ypf[i] = (float)(ypf[i]-y); } } else { for (int i=0; i<nPoints; i++) { xp[i] = xp[i]-x; yp[i] = yp[i]-y; } } if (nPoints<2 || (!(type==FREELINE||type==POLYLINE||type==ANGLE) && (nPoints<3||width==0||height==0))) { if (imp!=null) imp.killRoi(); if (type!=POINT) return; } state = NORMAL; if (imp!=null && !(type==TRACED_ROI)) imp.draw(x-5, y-5, width+10, height+10); oldX=x; oldY=y; oldWidth=width; oldHeight=height; if (Recorder.record && userCreated && (type==POLYGON||type==POLYLINE||type==ANGLE ||(type==POINT&&Recorder.scriptMode()&&nPoints==3))) Recorder.recordRoi(getPolygon(), type); if (type!=POINT) modifyRoi(); LineWidthAdjuster.update(); } public void exitConstructingMode() { if (type==POLYLINE && state==CONSTRUCTING) { addOffset(); finishPolygon(); } } protected void moveHandle(int sx, int sy) { if (clipboard!=null) return; int ox = ic.offScreenX(sx); int oy = ic.offScreenY(sy); xp[activeHandle] = ox-x; yp[activeHandle] = oy-y; if (xSpline!=null) { fitSpline(splinePoints); updateClipRect(); imp.draw(clipX, clipY, clipWidth, clipHeight); oldX = x; oldY = y; oldWidth = width; oldHeight = height; } else { resetBoundingRect(); if (type==POINT && width==0 && height==0) {width=1; height=1;} updateClipRectAndDraw(); } String angle = type==ANGLE?getAngleAsString():""; IJ.showStatus(imp.getLocationAsString(ox,oy) + angle); } /** After handle is moved, find clip rect and repaint. */ void updateClipRectAndDraw() { int xmin=Integer.MAX_VALUE, ymin=Integer.MAX_VALUE, xmax=0, ymax=0; int x2, y2; if (activeHandle>0) {x2=x+xp[activeHandle-1]; y2=y+yp[activeHandle-1];} else {x2=x+xp[nPoints-1]; y2=y+yp[nPoints-1];} if (x2<xmin) xmin = x2; if (y2<ymin) ymin = y2; if (x2>xmax) xmax = x2; if (y2>ymax) ymax = y2; x2=x+xp[activeHandle]; y2=y+yp[activeHandle]; if (x2<xmin) xmin = x2; if (y2<ymin) ymin = y2; if (x2>xmax) xmax = x2; if (y2>ymax) ymax = y2; if (activeHandle<nPoints-1) {x2=x+xp[activeHandle+1]; y2=y+yp[activeHandle+1];} else {x2=x+xp[0]; y2=y+yp[0];} if (x2<xmin) xmin = x2; if (y2<ymin) ymin = y2; if (x2>xmax) xmax = x2; if (y2>ymax) ymax = y2; int xmin2=xmin, ymin2=ymin, xmax2=xmax, ymax2=ymax; if (xClipMin<xmin2) xmin2 = xClipMin; if (yClipMin<ymin2) ymin2 = yClipMin; if (xClipMax>xmax2) xmax2 = xClipMax; if (yClipMax>ymax2) ymax2 = yClipMax; xClipMin=xmin; yClipMin=ymin; xClipMax=xmax; yClipMax=ymax; double mag = ic.getMagnification(); int handleSize = type==POINT?HANDLE_SIZE+8:HANDLE_SIZE; if (handleSize<getStrokeWidth() && isLine()) handleSize = (int)getStrokeWidth() ; int m = mag<1.0?(int)(handleSize/mag):handleSize; m = (int)(m*getStrokeWidth()); imp.draw(xmin2-m, ymin2-m, xmax2-xmin2+m*2, ymax2-ymin2+m*2); } void resetBoundingRect() { int xmin=Integer.MAX_VALUE, xmax=-xmin, ymin=xmin, ymax=xmax; int xx, yy; for(int i=0; i<nPoints; i++) { xx = xp[i]; if (xx<xmin) xmin=xx; if (xx>xmax) xmax=xx; yy = yp[i]; if (yy<ymin) ymin=yy; if (yy>ymax) ymax=yy; } if (xmin!=0) for (int i=0; i<nPoints; i++) xp[i] -= xmin; if (ymin!=0) for (int i=0; i<nPoints; i++) yp[i] -= ymin; //IJ.log("reset: "+ymin+" "+before+" "+yp[0]); x+=xmin; y+=ymin; width=xmax-xmin; height=ymax-ymin; } String getAngleAsString() { double angle1 = getAngle(xp[0], yp[0], xp[1], yp[1]); double angle2 = getAngle(xp[1], yp[1], xp[2], yp[2]); degrees = Math.abs(180-Math.abs(angle1-angle2)); if (degrees>180.0) degrees = 360.0-degrees; double degrees2 = Prefs.reflexAngle&&type==ANGLE?360.0-degrees:degrees; return ", angle=" + IJ.d2s(degrees2); } protected void mouseDownInHandle(int handle, int sx, int sy) { if (state==CONSTRUCTING) return; int ox=ic.offScreenX(sx), oy=ic.offScreenY(sy); if (IJ.altKeyDown() && !(nPoints<=3 && type!=POINT)) { deleteHandle(ox, oy); return; } else if (IJ.shiftKeyDown() && type!=POINT) { addHandle(ox, oy); return; } state = MOVING_HANDLE; activeHandle = handle; int m = (int)(10.0/ic.getMagnification()); xClipMin=ox-m; yClipMin=oy-m; xClipMax=ox+m; yClipMax=oy+m; } public void deleteHandle(int ox, int oy) { if (imp==null) return; if (nPoints<=1) {imp.killRoi(); return;} boolean splineFit = xSpline != null; xSpline = null; Polygon points = getPolygon(); modState = NO_MODS; if (previousRoi!=null) previousRoi.modState = NO_MODS; int pointToDelete = getClosestPoint(ox, oy, points); Polygon points2 = new Polygon(); for (int i=0; i<points.npoints; i++) { if (i!=pointToDelete) points2.addPoint(points.xpoints[i], points.ypoints[i]); } if (type==POINT) imp.setRoi(new PointRoi(points2.xpoints, points2.ypoints, points2.npoints)); else { imp.setRoi(new PolygonRoi(points2, type)); if (splineFit) ((PolygonRoi)imp.getRoi()).fitSpline(splinePoints); } } void addHandle(int ox, int oy) { if (imp==null || type==ANGLE) return; boolean splineFit = xSpline != null; xSpline = null; Polygon points = getPolygon(); int n = points.npoints; modState = NO_MODS; if (previousRoi!=null) previousRoi.modState = NO_MODS; int pointToDuplicate = getClosestPoint(ox, oy, points); Polygon points2 = new Polygon(); for (int i2=0; i2<n; i2++) { if (i2==pointToDuplicate) { int i1 = i2-1; if (i1==-1) i1 = isLine()?i2:n-1; int i3 = i2+1; if (i3==n) i3 = isLine()?i2:0; int x1 = points.xpoints[i1] + 2*(points.xpoints[i2] - points.xpoints[i1])/3; int y1 = points.ypoints[i1] + 2*(points.ypoints[i2] - points.ypoints[i1])/3; int x2 = points.xpoints[i2] + (points.xpoints[i3] - points.xpoints[i2])/3; int y2 = points.ypoints[i2] + (points.ypoints[i3] - points.ypoints[i2])/3; points2.addPoint(x1, y1); points2.addPoint(x2, y2); } else points2.addPoint(points.xpoints[i2], points.ypoints[i2]); } if (type==POINT) imp.setRoi(new PointRoi(points2.xpoints, points2.ypoints, points2.npoints)); else { imp.setRoi(new PolygonRoi(points2, type)); if (splineFit) ((PolygonRoi)imp.getRoi()).fitSpline(splinePoints); } } int getClosestPoint(int x, int y, Polygon points) { int index = 0; double distance = Double.MAX_VALUE; for (int i=0; i<points.npoints; i++) { double dx = points.xpoints[i] - x; double dy = points.ypoints[i] - y; double distance2 = dx*dx+dy*dy; if (distance2<distance) { distance = distance2; index = i; } } return index; } public void fitSpline(int evaluationPoints) { if (xSpline==null || splinePoints!=evaluationPoints) { splinePoints = evaluationPoints; xSpline = new float[splinePoints]; ySpline = new float[splinePoints]; } int nNodes = nPoints; if (type==POLYGON) { nNodes++; if (nNodes>=xp.length) enlargeArrays(); xp[nNodes-1] = xp[0]; yp[nNodes-1] = yp[0]; } int[] xindex = new int[nNodes]; for(int i=0; i<nNodes; i++) xindex[i] = i; SplineFitter sfx = new SplineFitter(xindex, xp, nNodes); SplineFitter sfy = new SplineFitter(xindex, yp, nNodes); // Evaluate the splines at all points double scale = (double)(nNodes-1)/(splinePoints-1); float xs=0f, ys=0f; float xmin=Float.MAX_VALUE, xmax=-xmin, ymin=xmin, ymax=xmax; for(int i=0; i<splinePoints; i++) { double xvalue = i*scale; xs = (float)sfx.evalSpline(xindex, xp, nNodes, xvalue); if (xs<xmin) xmin=xs; if (xs>xmax) xmax=xs; xSpline[i] = xs; ys = (float)sfy.evalSpline(xindex, yp, nNodes, xvalue); if (ys<ymin) ymin=ys; if (ys>ymax) ymax=ys; ySpline[i] = ys; } int ixmin = (int)Math.floor(xmin+0.5f); int ixmax = (int)Math.floor(xmax+0.5f); int iymin = (int)Math.floor(ymin+0.5f); int iymax = (int)Math.floor(ymax+0.5f); if (ixmin!=0) { for (int i=0; i<nPoints; i++) xp[i] -= ixmin; for (int i=0; i<splinePoints; i++) xSpline[i] -= ixmin; } if (iymin!=0) { for (int i=0; i<nPoints; i++) yp[i] -= iymin; for (int i=0; i<splinePoints; i++) ySpline[i] -= iymin; } x+=ixmin; y+=iymin; width=ixmax-ixmin; height=iymax-iymin; cachedMask = null; } public void fitSpline() { double length = getUncalibratedLength(); int evaluationPoints = (int)(length/2.0); if (ic!=null) { double mag = ic.getMagnification(); if (mag<1.0) evaluationPoints *= mag;; } if (evaluationPoints<100) evaluationPoints = 100; fitSpline(evaluationPoints); } public void removeSplineFit() { xSpline = null; ySpline = null; } /** Returns 'true' if this selection has been fitted with a spline. */ public boolean isSplineFit() { return xSpline!=null; } /* Creates a spline fitted polygon with one pixel segment lengths that can be retrieved using the getFloatPolygon() method. */ public void fitSplineForStraightening() { fitSpline((int)getUncalibratedLength()*2); if (splinePoints==0) return; float[] xpoints = new float[splinePoints*2]; float[] ypoints = new float[splinePoints*2]; xpoints[0] = xSpline[0]; ypoints[0] = ySpline[0]; int n=1, n2; double inc = 0.01; double distance=0.0, distance2=0.0, dx=0.0, dy=0.0, xinc, yinc; double x, y, lastx, lasty, x1, y1, x2=xSpline[0], y2=ySpline[0]; for (int i=1; i<splinePoints; i++) { x1=x2; y1=y2; x=x1; y=y1; x2=xSpline[i]; y2=ySpline[i]; dx = x2-x1; dy = y2-y1; distance = Math.sqrt(dx*dx+dy*dy); xinc = dx*inc/distance; yinc = dy*inc/distance; lastx=xpoints[n-1]; lasty=ypoints[n-1]; //n2 = (int)(dx/xinc); n2 = (int)(distance/inc); if (splinePoints==2) n2++; do { dx = x-lastx; dy = y-lasty; distance2 = Math.sqrt(dx*dx+dy*dy); //IJ.log(i+" "+IJ.d2s(xinc,5)+" "+IJ.d2s(yinc,5)+" "+IJ.d2s(distance,2)+" "+IJ.d2s(distance2,2)+" "+IJ.d2s(x,2)+" "+IJ.d2s(y,2)+" "+IJ.d2s(lastx,2)+" "+IJ.d2s(lasty,2)+" "+n+" "+n2); if (distance2>=1.0-inc/2.0 && n<xpoints.length-1) { xpoints[n] = (float)x; ypoints[n] = (float)y; //IJ.log("--- "+IJ.d2s(x,2)+" "+IJ.d2s(y,2)+" "+n); n++; lastx=x; lasty=y; } x += xinc; y += yinc; } while (--n2>0); } xSpline = xpoints; ySpline = ypoints; splinePoints = n; } public double getUncalibratedLength() { ImagePlus saveImp = imp; imp = null; double length = getLength(); imp = saveImp; return length; } protected void handleMouseUp(int sx, int sy) { if (state==MOVING) {state = NORMAL; return;} if (state==MOVING_HANDLE) { cachedMask = null; //mask is no longer valid state = NORMAL; updateClipRect(); oldX=x; oldY=y; oldWidth=width; oldHeight=height; return; } if (state!=CONSTRUCTING) return; if (IJ.spaceBarDown()) // is user scrolling image? return; boolean samePoint = (xp[nPoints-2]==xp[nPoints-1] && yp[nPoints-2]==yp[nPoints-1]); Rectangle biggerStartBox = new Rectangle(ic.screenX(startX)-5, ic.screenY(startY)-5, 10, 10); if (nPoints>2 && (biggerStartBox.contains(sx, sy) || (ic.offScreenX(sx)==startX && ic.offScreenY(sy)==startY) || (samePoint && (System.currentTimeMillis()-mouseUpTime)<=500))) { nPoints--; addOffset(); finishPolygon(); return; } else if (!samePoint) { mouseUpTime = System.currentTimeMillis(); if (type==ANGLE && nPoints==3) { addOffset(); finishPolygon(); return; } //add point to polygon xp[nPoints] = xp[nPoints-1]; yp[nPoints] = yp[nPoints-1]; nPoints++; if (nPoints==xp.length) enlargeArrays(); //if (lineWidth>1) fitSpline(); } } protected void addOffset() { if (xpf!=null) { for (int i=0; i<nPoints; i++) { xpf[i] = (float)(xpf[i]+x); ypf[i] = (float)(ypf[i]+y); } } else { for (int i=0; i<nPoints; i++) { xp[i] = xp[i]+x; yp[i] = yp[i]+y; } } } public boolean contains(int x, int y) { if (!super.contains(x, y)) return false; if (xSpline!=null) { FloatPolygon poly = new FloatPolygon(xSpline, ySpline, splinePoints); return poly.contains(x-this.x, y-this.y); } else if (xpf!=null) { FloatPolygon poly = new FloatPolygon(xpf, ypf, nPoints); return poly.contains(x-this.x, y-this.y); } else { Polygon poly = new Polygon(xp, yp, nPoints); return poly.contains(x-this.x, y-this.y); } } /** Returns a handle number if the specified screen coordinates are inside or near a handle, otherwise returns -1. */ public int isHandle(int sx, int sy) { if (!(xSpline!=null||type==POLYGON||type==POLYLINE||type==ANGLE||type==POINT)||clipboard!=null) return -1; int size = HANDLE_SIZE+5; int halfSize = size/2; int handle = -1; int sx2, sy2; for (int i=0; i<nPoints; i++) { sx2 = xp2[i]-halfSize; sy2=yp2[i]-halfSize; if (sx>=sx2 && sx<=sx2+size && sy>=sy2 && sy<=sy2+size) { handle = i; break; } } return handle; } /** Override Roi.nudge() to support splines. */ //public void nudge(int key) { // super.nudge(key); // if (xSpline!=null) { // fitSpline(); // updateFullWindow = true; // imp.draw(); // } //} public ImageProcessor getMask() { if (cachedMask!=null && cachedMask.getPixels()!=null) return cachedMask; PolygonFiller pf = new PolygonFiller(); if (xSpline!=null) pf.setPolygon(toInt(xSpline), toInt(ySpline), splinePoints); else if (xpf!=null) pf.setPolygon(toInt(xpf), toInt(ypf), nPoints); else pf.setPolygon(xp, yp, nPoints); cachedMask = pf.getMask(width, height); return cachedMask; } /** Returns the length of this line selection after smoothing using a 3-point running average.*/ double getSmoothedLineLength() { double length = 0.0; double w2 = 1.0; double h2 = 1.0; double dx, dy; if (imp!=null) { Calibration cal = imp.getCalibration(); w2 = cal.pixelWidth*cal.pixelWidth; h2 = cal.pixelHeight*cal.pixelHeight; } dx = (xp[0]+xp[1]+xp[2])/3.0-xp[0]; dy = (yp[0]+yp[1]+yp[2])/3.0-yp[0]; length += Math.sqrt(dx*dx*w2+dy*dy*h2); for (int i=1; i<nPoints-2; i++) { dx = (xp[i+2]-xp[i-1])/3.0; // = (x[i]+x[i+1]+x[i+2])/3-(x[i-1]+x[i]+x[i+1])/3 dy = (yp[i+2]-yp[i-1])/3.0; // = (y[i]+y[i+1]+y[i+2])/3-(y[i-1]+y[i]+y[i+1])/3 length += Math.sqrt(dx*dx*w2+dy*dy*h2); } dx = xp[nPoints-1]-(xp[nPoints-3]+xp[nPoints-2]+xp[nPoints-1])/3.0; dy = yp[nPoints-1]-(yp[nPoints-3]+yp[nPoints-2]+yp[nPoints-1])/3.0; length += Math.sqrt(dx*dx*w2+dy*dy*h2); return length; } /** Returns the perimeter of this ROI after smoothing using a 3-point running average.*/ double getSmoothedPerimeter() { double length = getSmoothedLineLength(); double w2=1.0, h2=1.0; if (imp!=null) { Calibration cal = imp.getCalibration(); w2 = cal.pixelWidth*cal.pixelWidth; h2 = cal.pixelHeight*cal.pixelHeight; } double dx = xp[nPoints-1]-xp[0]; double dy = yp[nPoints-1]-yp[0]; length += Math.sqrt(dx*dx*w2+dy*dy*h2); return length; } /** Returns the perimeter length of ROIs created using the wand tool and the particle analyzer. The algorithm counts edge pixels as 1 and corner pixels as sqrt(2). It does this by calculating the total length of the ROI boundary and subtracting 2-sqrt(2) for each non-adjacent corner. For example, a 1x1 pixel ROI has a boundary length of 4 and 2 non-adjacent edges so the perimeter is 4-2*(2-sqrt(2)). A 2x2 pixel ROI has a boundary length of 8 and 4 non-adjacent edges so the perimeter is 8-4*(2-sqrt(2)). */ double getTracedPerimeter() { int sumdx = 0; int sumdy = 0; int nCorners = 0; int dx1 = xp[0] - xp[nPoints-1]; int dy1 = yp[0] - yp[nPoints-1]; int side1 = Math.abs(dx1) + Math.abs(dy1); //one of these is 0 boolean corner = false; int nexti, dx2, dy2, side2; for (int i=0; i<nPoints; i++) { nexti = i+1; if (nexti==nPoints) nexti = 0; dx2 = xp[nexti] - xp[i]; dy2 = yp[nexti] - yp[i]; sumdx += Math.abs(dx1); sumdy += Math.abs(dy1); side2 = Math.abs(dx2) + Math.abs(dy2); if (side1>1 || !corner) { corner = true; nCorners++; } else corner = false; dx1 = dx2; dy1 = dy2; side1 = side2; } double w=1.0,h=1.0; if (imp!=null) { Calibration cal = imp.getCalibration(); w = cal.pixelWidth; h = cal.pixelHeight; } return sumdx*w+sumdy*h-(nCorners*((w+h)-Math.sqrt(w*w+h*h))); } /** Returns the perimeter (for ROIs) or length (for lines).*/ public double getLength() { if (type==TRACED_ROI) return getTracedPerimeter(); if (nPoints>2) { if (type==FREEROI) return getSmoothedPerimeter(); else if (type==FREELINE && !(width==0 || height==0)) return getSmoothedLineLength(); } double length = 0.0; int dx, dy; double w2=1.0, h2=1.0; if (imp!=null) { Calibration cal = imp.getCalibration(); w2 = cal.pixelWidth*cal.pixelWidth; h2 = cal.pixelHeight*cal.pixelHeight; } if (xSpline!=null) { double fdx, fdy; for (int i=0; i<(splinePoints-1); i++) { fdx = xSpline[i+1]-xSpline[i]; fdy = ySpline[i+1]-ySpline[i]; length += Math.sqrt(fdx*fdx*w2+fdy*fdy*h2); } if (type==POLYGON) { fdx = xSpline[0]-xSpline[splinePoints-1]; fdy = ySpline[0]-ySpline[splinePoints-1]; length += Math.sqrt(fdx*fdx*w2+fdy*fdy*h2); } } else { for (int i=0; i<(nPoints-1); i++) { dx = xp[i+1]-xp[i]; dy = yp[i+1]-yp[i]; length += Math.sqrt(dx*dx*w2+dy*dy*h2); } if (type==POLYGON) { dx = xp[0]-xp[nPoints-1]; dy = yp[0]-yp[nPoints-1]; length += Math.sqrt(dx*dx*w2+dy*dy*h2); } } return length; } /** Returns the angle in degrees between the first two segments of this polyline.*/ public double getAngle() { return degrees; } /** Returns the number of XY coordinates. */ public int getNCoordinates() { if (xSpline!=null) return splinePoints; else return nPoints; } /** Obsolete; replaced by either getPolygon() or getFloatPolygon(). */ public int[] getXCoordinates() { if (xSpline!=null) return toInt(xSpline); else if (xpf!=null) return toInt(xpf); else return xp; } /** Obsolete; replaced by either getPolygon() or getFloatPolygon(). */ public int[] getYCoordinates() { if (xSpline!=null) return toInt(ySpline); else if (xpf!=null) return toInt(ypf); else return yp; } public Polygon getNonSplineCoordinates() { if (xpf!=null) return new Polygon(toInt(xpf), toInt(ypf), nPoints); else return new Polygon(xp, yp, nPoints); } /** Returns this PolygonRoi as a Polygon. @see ij.process.ImageProcessor#setRoi @see ij.process.ImageProcessor#drawPolygon @see ij.process.ImageProcessor#fillPolygon */ public Polygon getPolygon() { int n; int[] xpoints1, ypoints1; if (xSpline!=null) { n = splinePoints; xpoints1 = toInt(xSpline); ypoints1 = toInt(ySpline); } else if (xpf!=null) { n = nPoints; xpoints1 = toInt(xpf); ypoints1 = toInt(xpf); } else { n = nPoints; xpoints1 = xp; ypoints1 = yp; } int[] xpoints2 = new int[n]; int[] ypoints2 = new int[n]; for (int i=0; i<n; i++) { xpoints2[i] = xpoints1[i] + x; ypoints2[i] = ypoints1[i] + y; } return new Polygon(xpoints2, ypoints2, n); } /** Returns this polygon or polyline as float arrays. */ public FloatPolygon getFloatPolygon() { int n = xSpline!=null?splinePoints:nPoints; float[] xpoints2 = new float[n]; float[] ypoints2 = new float[n]; if (xSpline!=null) { for (int i=0; i<n; i++) { xpoints2[i] = xSpline[i] + x; ypoints2[i] = ySpline[i] + y; } } else if (xpf!=null) { for (int i=0; i<n; i++) { xpoints2[i] = xpf[i] + x; ypoints2[i] = ypf[i] + y; } } else { for (int i=0; i<n; i++) { xpoints2[i] = xp[i] + x; ypoints2[i] = yp[i] + y; } } return new FloatPolygon(xpoints2, ypoints2, n); } /** Uses the gift wrap algorithm to find the convex hull and returns it as a Polygon. */ public Polygon getConvexHull() { int n = getNCoordinates(); int[] xCoordinates = getXCoordinates(); int[] yCoordinates = getYCoordinates(); Rectangle r = getBounds(); int xbase = r.x; int ybase = r.y; int[] xx = new int[n]; int[] yy = new int[n]; int n2 = 0; int smallestY = Integer.MAX_VALUE; int x, y; for (int i=0; i<n; i++) { y = yCoordinates[i]; if (y<smallestY) smallestY = y; } int smallestX = Integer.MAX_VALUE; int p1 = 0; for (int i=0; i<n; i++) { x = xCoordinates[i]; y = yCoordinates[i]; if (y==smallestY && x<smallestX) { smallestX = x; p1 = i; } } int pstart = p1; int x1, y1, x2, y2, x3, y3, p2, p3; int determinate; int count = 0; do { x1 = xCoordinates[p1]; y1 = yCoordinates[p1]; p2 = p1+1; if (p2==n) p2=0; x2 = xCoordinates[p2]; y2 = yCoordinates[p2]; p3 = p2+1; if (p3==n) p3=0; do { x3 = xCoordinates[p3]; y3 = yCoordinates[p3]; determinate = x1*(y2-y3)-y1*(x2-x3)+(y3*x2-y2*x3); if (determinate>0) {x2=x3; y2=y3; p2=p3;} p3 += 1; if (p3==n) p3 = 0; } while (p3!=p1); if (n2<n) { xx[n2] = xbase + x1; yy[n2] = ybase + y1; n2++; } else { count++; if (count>10) return null; } p1 = p2; } while (p1!=pstart); return new Polygon(xx, yy, n2); } protected int clipRectMargin() { return type==POINT?4:0; } /** Returns a copy of this PolygonRoi. */ public synchronized Object clone() { PolygonRoi r = (PolygonRoi)super.clone(); if (xpf!=null) { r.xpf = new float[maxPoints]; r.ypf = new float[maxPoints]; } else { r.xp = new int[maxPoints]; r.yp = new int[maxPoints]; } r.xp2 = new int[maxPoints]; r.yp2 = new int[maxPoints]; for (int i=0; i<nPoints; i++) { if (xpf!=null) { r.xpf[i] = xpf[i]; r.ypf[i] = ypf[i]; } else { r.xp[i] = xp[i]; r.yp[i] = yp[i]; } r.xp2[i] = xp2[i]; r.yp2[i] = yp2[i]; } if (xSpline!=null) { r.xSpline = new float[splinePoints]; r.ySpline = new float[splinePoints]; r.splinePoints = splinePoints; for (int i=0; i<splinePoints; i++) { r.xSpline[i] = xSpline[i]; r.ySpline[i] = ySpline[i]; } } return r; } void enlargeArrays() { int[] xptemp = new int[maxPoints*2]; int[] yptemp = new int[maxPoints*2]; int[] xp2temp = new int[maxPoints*2]; int[] yp2temp = new int[maxPoints*2]; System.arraycopy(xp, 0, xptemp, 0, maxPoints); System.arraycopy(yp, 0, yptemp, 0, maxPoints); System.arraycopy(xp2, 0, xp2temp, 0, maxPoints); System.arraycopy(yp2, 0, yp2temp, 0, maxPoints); xp=xptemp; yp=yptemp; xp2=xp2temp; yp2=yp2temp; if (IJ.debugMode) IJ.log("PolygonRoi: "+maxPoints+" points"); maxPoints *= 2; } }