package endrov.IJ.roi; 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; i1 && 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 (activeHandle1f) 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; i1) { 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 (x1xmax) xmax=x1; if (x2>xmax) xmax=x2; if (ox>xmax) xmax=ox; if (y1ymax) 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; i0) {x2=x+xp[activeHandle-1]; y2=y+yp[activeHandle-1];} else {x2=x+xp[nPoints-1]; y2=y+yp[nPoints-1];} if (x2xmax) xmax = x2; if (y2>ymax) ymax = y2; x2=x+xp[activeHandle]; y2=y+yp[activeHandle]; if (x2xmax) xmax = x2; if (y2>ymax) ymax = y2; if (activeHandlexmax) xmax = x2; if (y2>ymax) ymax = y2; int xmin2=xmin, ymin2=ymin, xmax2=xmax, ymax2=ymax; if (xClipMinxmax2) 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 (handleSizexmax) xmax=xx; yy = yp[i]; if (yyymax) ymax=yy; } if (xmin!=0) for (int i=0; i180.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=xp.length) enlargeArrays(); xp[nNodes-1] = xp[0]; yp[nNodes-1] = yp[0]; } int[] xindex = new int[nNodes]; for(int i=0; ixmax) xmax=xs; xSpline[i] = xs; ys = (float)sfy.evalSpline(xindex, yp, nNodes, xvalue); if (ysymax) 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=1.0-inc/2.0 && n0); } 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=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; i1 || !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; } /** Returns this ROI's X-coordinates, which are relative to origin of the bounding box. */ public int[] getXCoordinates() { if (xSpline!=null) return toInt(xSpline); else if (xpf!=null) return toInt(xpf); else return xp; } /** Returns this ROI's Y-coordinates, which are relative to origin of the bounding box. */ 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; i0) {x2=x3; y2=y3; p2=p3;} p3 += 1; if (p3==n) p3 = 0; } while (p3!=p1); if (n210) 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