/* 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; }
/** * 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))); }
void handleMouseMove(int sx, int sy) { // Do rubber banding int tool = Toolbar.getToolId(); if (!(tool == Toolbar.POLYGON || tool == Toolbar.POLYLINE || tool == Toolbar.ANGLE)) { imp.deleteRoi(); imp.draw(); return; } drawRubberBand(sx, sy); degrees = Double.NaN; double len = -1; if (nPoints > 1) { double x1, y1, x2, y2; if (xpf != null) { x1 = xpf[nPoints - 2]; y1 = ypf[nPoints - 2]; x2 = xpf[nPoints - 1]; y2 = ypf[nPoints - 1]; } else { x1 = xp[nPoints - 2]; y1 = yp[nPoints - 2]; x2 = xp[nPoints - 1]; y2 = yp[nPoints - 1]; } degrees = getAngle( (int) Math.round(x1), (int) Math.round(y1), (int) Math.round(x2), (int) Math.round(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) : ""; int ox = ic != null ? ic.offScreenX(sx) : sx; int oy = ic != null ? ic.offScreenY(sy) : sy; IJ.showStatus(imp.getLocationAsString(ox, oy) + length + angle); }
PolygonRoi trimPolygon(PolygonRoi roi, double length) { int[] x = roi.getXCoordinates(); int[] y = roi.getYCoordinates(); int n = roi.getNCoordinates(); x = smooth(x, n); y = smooth(y, n); float[] curvature = getCurvature(x, y, n); Rectangle r = roi.getBounds(); double threshold = rodbard(length); // IJ.log("trim: "+length+" "+threshold); double distance = Math.sqrt((x[1] - x[0]) * (x[1] - x[0]) + (y[1] - y[0]) * (y[1] - y[0])); x[0] += r.x; y[0] += r.y; int i2 = 1; int x1, y1, x2 = 0, y2 = 0; for (int i = 1; i < n - 1; i++) { x1 = x[i]; y1 = y[i]; x2 = x[i + 1]; y2 = y[i + 1]; distance += Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)) + 1; distance += curvature[i] * 2; if (distance >= threshold) { x[i2] = x2 + r.x; y[i2] = y2 + r.y; i2++; distance = 0.0; } } int type = roi.getType() == Roi.FREELINE ? Roi.POLYLINE : Roi.POLYGON; if (type == Roi.POLYLINE && distance > 0.0) { x[i2] = x2 + r.x; y[i2] = y2 + r.y; i2++; } PolygonRoi p = new PolygonRoi(x, y, i2, type); if (roi.getStroke() != null) p.setStrokeWidth(roi.getStrokeWidth()); p.setStrokeColor(roi.getStrokeColor()); p.setName(roi.getName()); imp.setRoi(p); return p; }
PolygonRoi trimFloatPolygon(PolygonRoi roi, double length) { FloatPolygon poly = roi.getFloatPolygon(); float[] x = poly.xpoints; float[] y = poly.ypoints; int n = poly.npoints; x = smooth(x, n); y = smooth(y, n); float[] curvature = getCurvature(x, y, n); double threshold = rodbard(length); // IJ.log("trim: "+length+" "+threshold); double distance = Math.sqrt((x[1] - x[0]) * (x[1] - x[0]) + (y[1] - y[0]) * (y[1] - y[0])); int i2 = 1; double x1, y1, x2 = 0, y2 = 0; for (int i = 1; i < n - 1; i++) { x1 = x[i]; y1 = y[i]; x2 = x[i + 1]; y2 = y[i + 1]; distance += Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)) + 1; distance += curvature[i] * 2; if (distance >= threshold) { x[i2] = (float) x2; y[i2] = (float) y2; i2++; distance = 0.0; } } int type = roi.getType() == Roi.FREELINE ? Roi.POLYLINE : Roi.POLYGON; if (type == Roi.POLYLINE && distance > 0.0) { x[i2] = (float) x2; y[i2] = (float) y2; i2++; } PolygonRoi p = new PolygonRoi(x, y, i2, type); if (roi.getStroke() != null) p.setStrokeWidth(roi.getStrokeWidth()); p.setStrokeColor(roi.getStrokeColor()); p.setDrawOffset(roi.getDrawOffset()); p.setName(roi.getName()); imp.setRoi(p); return p; }
double getFloatSmoothedPerimeter() { 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 = xpf[nPoints - 1] - xpf[0]; double dy = ypf[nPoints - 1] - ypf[0]; length += Math.sqrt(dx * dx * w2 + dy * dy * h2); return length; }
double getFloatSmoothedLineLength() { 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 = (xpf[0] + xpf[1] + xpf[2]) / 3.0 - xpf[0]; dy = (ypf[0] + ypf[1] + ypf[2]) / 3.0 - ypf[0]; length += Math.sqrt(dx * dx * w2 + dy * dy * h2); for (int i = 1; i < nPoints - 2; i++) { dx = (xpf[i + 2] - xpf[i - 1]) / 3.0; dy = (ypf[i + 2] - ypf[i - 1]) / 3.0; length += Math.sqrt(dx * dx * w2 + dy * dy * h2); } dx = xpf[nPoints - 1] - (xpf[nPoints - 3] + xpf[nPoints - 2] + xpf[nPoints - 1]) / 3.0; dy = ypf[nPoints - 1] - (ypf[nPoints - 3] + ypf[nPoints - 2] + ypf[nPoints - 1]) / 3.0; length += Math.sqrt(dx * dx * w2 + dy * dy * h2); return length; }
/** 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 if (xpf != null) { double fdx, fdy; for (int i = 0; i < (nPoints - 1); i++) { fdx = xpf[i + 1] - xpf[i]; fdy = ypf[i + 1] - ypf[i]; length += Math.sqrt(fdx * fdx * w2 + fdy * fdy * h2); } if (type == POLYGON) { fdx = xpf[0] - xpf[nPoints - 1]; fdy = ypf[0] - ypf[nPoints - 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; }
float[] getCurvature(float[] x, float[] y, int n) { float[] x2 = new float[n]; float[] y2 = new float[n]; for (int i = 0; i < n; i++) { x2[i] = x[i]; y2[i] = y[i]; } ImageProcessor ipx = new FloatProcessor(n, 1, x, null); ImageProcessor ipy = new FloatProcessor(n, 1, y, null); ipx.convolve(kernel, kernel.length, 1); ipy.convolve(kernel, kernel.length, 1); float[] indexes = new float[n]; float[] curvature = new float[n]; for (int i = 0; i < n; i++) { indexes[i] = i; curvature[i] = (float) Math.sqrt((x2[i] - x[i]) * (x2[i] - x[i]) + (y2[i] - y[i]) * (y2[i] - y[i])); } return curvature; }
/* if selection is closed shape, create a circle with the same area and centroid, otherwise use<br> the Pratt method to fit a circle to the points that define the line or multi-point selection.<br> Reference: Pratt V., Direct least-squares fitting of algebraic surfaces", Computer Graphics, Vol. 21, pages 145-152 (1987).<br> Original code: Nikolai Chernov's MATLAB script for Newton-based Pratt fit.<br> (http://www.math.uab.edu/~chernov/cl/MATLABcircle.html)<br> Java version: https://github.com/mdoube/BoneJ/blob/master/src/org/doube/geometry/FitCircle.java<br> @authors Nikolai Chernov, Michael Doube, Ved Sharma */ void fitCircle(ImagePlus imp) { Roi roi = imp.getRoi(); if (roi == null) { noRoi("Fit Circle"); return; } if (roi.isArea()) { // create circle with the same area and centroid ImageProcessor ip = imp.getProcessor(); ip.setRoi(roi); ImageStatistics stats = ImageStatistics.getStatistics(ip, Measurements.AREA + Measurements.CENTROID, null); double r = Math.sqrt(stats.pixelCount / Math.PI); imp.killRoi(); int d = (int) Math.round(2.0 * r); IJ.makeOval( (int) Math.round(stats.xCentroid - r), (int) Math.round(stats.yCentroid - r), d, d); return; } Polygon poly = roi.getPolygon(); int n = poly.npoints; int[] x = poly.xpoints; int[] y = poly.ypoints; if (n < 3) { IJ.error("Fit Circle", "At least 3 points are required to fit a circle."); return; } // calculate point centroid double sumx = 0, sumy = 0; for (int i = 0; i < n; i++) { sumx = sumx + poly.xpoints[i]; sumy = sumy + poly.ypoints[i]; } double meanx = sumx / n; double meany = sumy / n; // calculate moments double[] X = new double[n], Y = new double[n]; double Mxx = 0, Myy = 0, Mxy = 0, Mxz = 0, Myz = 0, Mzz = 0; for (int i = 0; i < n; i++) { X[i] = x[i] - meanx; Y[i] = y[i] - meany; double Zi = X[i] * X[i] + Y[i] * Y[i]; Mxy = Mxy + X[i] * Y[i]; Mxx = Mxx + X[i] * X[i]; Myy = Myy + Y[i] * Y[i]; Mxz = Mxz + X[i] * Zi; Myz = Myz + Y[i] * Zi; Mzz = Mzz + Zi * Zi; } Mxx = Mxx / n; Myy = Myy / n; Mxy = Mxy / n; Mxz = Mxz / n; Myz = Myz / n; Mzz = Mzz / n; // calculate the coefficients of the characteristic polynomial double Mz = Mxx + Myy; double Cov_xy = Mxx * Myy - Mxy * Mxy; double Mxz2 = Mxz * Mxz; double Myz2 = Myz * Myz; double A2 = 4 * Cov_xy - 3 * Mz * Mz - Mzz; double A1 = Mzz * Mz + 4 * Cov_xy * Mz - Mxz2 - Myz2 - Mz * Mz * Mz; double A0 = Mxz2 * Myy + Myz2 * Mxx - Mzz * Cov_xy - 2 * Mxz * Myz * Mxy + Mz * Mz * Cov_xy; double A22 = A2 + A2; double epsilon = 1e-12; double ynew = 1e+20; int IterMax = 20; double xnew = 0; int iterations = 0; // Newton's method starting at x=0 for (int iter = 1; iter <= IterMax; iter++) { iterations = iter; double yold = ynew; ynew = A0 + xnew * (A1 + xnew * (A2 + 4. * xnew * xnew)); if (Math.abs(ynew) > Math.abs(yold)) { if (IJ.debugMode) IJ.log("Fit Circle: wrong direction: |ynew| > |yold|"); xnew = 0; break; } double Dy = A1 + xnew * (A22 + 16 * xnew * xnew); double xold = xnew; xnew = xold - ynew / Dy; if (Math.abs((xnew - xold) / xnew) < epsilon) break; if (iter >= IterMax) { if (IJ.debugMode) IJ.log("Fit Circle: will not converge"); xnew = 0; } if (xnew < 0) { if (IJ.debugMode) IJ.log("Fit Circle: negative root: x = " + xnew); xnew = 0; } } if (IJ.debugMode) IJ.log("Fit Circle: n=" + n + ", xnew=" + IJ.d2s(xnew, 2) + ", iterations=" + iterations); // calculate the circle parameters double DET = xnew * xnew - xnew * Mz + Cov_xy; double CenterX = (Mxz * (Myy - xnew) - Myz * Mxy) / (2 * DET); double CenterY = (Myz * (Mxx - xnew) - Mxz * Mxy) / (2 * DET); double radius = Math.sqrt(CenterX * CenterX + CenterY * CenterY + Mz + 2 * xnew); if (Double.isNaN(radius)) { IJ.error("Fit Circle", "Points are collinear."); return; } CenterX = CenterX + meanx; CenterY = CenterY + meany; imp.killRoi(); IJ.makeOval( (int) Math.round(CenterX - radius), (int) Math.round(CenterY - radius), (int) Math.round(2 * radius), (int) Math.round(2 * radius)); }