//Alpha_Shape.ijm //version 1.02 20140713 - fixed bug that always included the first point on the left in an alpha shape //also made alpha scaled to the current image's scale //Jonathan Lake jil24@cornell.edu //version 1, 1.02 20140713 if (selectionType() != 10) { exit("A point selection is required."); } //pull the last-used alpha value from imageJ's preferences or use 0 as a default alpha = call("ij.Prefs.get", "alphashapes.alpha", "0"); alpha = parseFloat(alpha); if (isNaN(alpha)) {alpha=0;} //ask for alpha Dialog.create("Alpha Shape"); Dialog.addMessage("Computes a regularized two-dimensional alpha shape from a point selection."); Dialog.addMessage("This shape will not include isolated points or edges"); Dialog.addNumber("Alpha (in scaled units):", alpha); Dialog.show(); alpha = Dialog.getNumber(); alpha = parseFloat(alpha); //convert string to number //save the alpha value for later. call("ij.Prefs.set", "alphashapes.alpha", alpha); toUnscaled(alpha); //modifies alpha in place for scaling. //utility functions function swap(a,b) { c = b; b = a; a = c; } function removeelementsbyvalue(array, i) { //might remove more than one thing new = newArray(); narray = array.length; for (j=0;jtesty) != (polyy[r]>testy))) { if ((testx < (polyx[r]-polyx[q]) * (testy-polyy[q]) / (polyy[r]-polyy[q]) + polyx[q]) ) { c = !c; } } r = q; } return c; } //translated almost verbatim from line-segments-intersect.js by pgkelley4@gmail.com function crossProduct(rx,ry,sx,sy) { //returns the scalar magnitude of the crossproduct, which projects into the z axis return ((rx * sy) - (ry * sx)); } function intersectSegments(px1,py1,px2,py2,qx1,qy1,qx2,qy2) { //do line segments intersect? rx = px2-px1; ry = py2-py1; sx = qx2-qx1; sy = qy2-qy1; //vector components of the line segments //for debug // setColor(floor(random()*256),floor(random()*256),floor(random()*256)); // setLineWidth(2); // drawLine(px1,py1,px2,py2); // drawLine(qx1,qy1,qx2,qy2); // waitForUser("next step"); uNumerator = crossProduct(qx1-px1,qy1-py1,rx,ry); denominator = crossProduct(rx,ry,sx,sy); if (uNumerator == 0 && denominator == 0) { // colinear, so do they overlap? return (((qx1 - px1 < 0) != (qx1 - px2 < 0)) != ((qx2 - px1 < 0) != (qx2 - px2 < 0))) || ((qy1 - py1 < 0) != (qy1 - py2 < 0) != (qy2 - py1 < 0) != (qy2 - py2 < 0)); } if (denominator == 0) { // lines are paralell return 0; } u = uNumerator / denominator; t = crossProduct(qx1-px1,qy1-py1,sx,sy) / denominator; return ((t >= 0) && (t <= 1) && (u >= 0) && (u <= 1)); } function incircle (u,v,p,q) { ux = xpoints[u]; uy = ypoints[u]; vx = xpoints[v]; vy = ypoints[v]; px = xpoints[p]; py = ypoints[p]; qx = xpoints[q]; qy = ypoints[q]; uxy2 = ux*ux+uy*uy; vxy2 = vx*vx+vy*vy; pxy2 = px*px+py*py; qxy2 = qx*qx+qy*qy; //to save myself a lot of work I calculated the laplace expansion of the relevant 4x4 determinant symbolically using SymPy. incircledet = (px*qxy2*uy) - (px*qxy2*vy) - (px*qy*uxy2) + (px*qy*vxy2) + (px*uxy2*vy) - (px*uy*vxy2) - (pxy2*qx*uy) + (pxy2*qx*vy) + (pxy2*qy*ux) - (pxy2*qy*vx) - (pxy2*ux*vy) + (pxy2*uy*vx) + (py*qx*uxy2) - (py*qx*vxy2) - (py*qxy2*ux) + (py*qxy2*vx) + (py*ux*vxy2) - (py*uxy2*vx) - (qx*uxy2*vy) + (qx*uy*vxy2) + (qxy2*ux*vy) - (qxy2*uy*vx) - (qy*ux*vxy2) + (qy*uxy2*vx); orderdet = (px*uy) - (px*vy) - (py*ux) + (py*vx) + (ux*vy) - (uy*vx); // print(incircledet * orderdet); return (incircledet * orderdet < 0); } function trixy(tri){ tri = tri * 6; //tri id to coordinates coords = newArray(); for (p = 0; p < 3; p++) { coords = Array.concat(coords, xpoints[tris[tri+p]], ypoints[tris[tri+p]]); } return coords; } function makeborderpolyconvex(newpoint) { oldnborderseg = borderpoly.length; newborderpoly = newArray(); for(testpoint=0;testpointv) {swap(u,v);} // just make sure they're in order edgestacklength = edgestack.length; for (i=0;iv) {swap(u,v);} // just make sure they're in order edgeindex = getedgeindex(u,v); before = Array.slice(edgestack,0,edgeindex); after = Array.slice(edgestack,edgeindex+2,edgestack.length); edgestack = Array.concat(before,after); } function edgeflipifnotld (u,v) { // Array.print(edgestack); deleteedge(u,v); //remove from the stack //returns 1 if an edge got flipped, 0 if no flip neccesary //this function won't check for an edge on the outside. tristoreplace = trisborderingedge(u,v); p = getotherpoint(tristoreplace[0],u,v); q = getotherpoint(tristoreplace[1],u,v); // print(p);print(q); //u and v represent a currently existing edge, p and q are the other points forming the triangles // print(tris[tristoreplace[1]*6],tris[tristoreplace[1]*6+1],tris[tristoreplace[1]*6+2]); // print(u,v,p,q); if (incircle(u,v,p,q)) { //if this is true then we are non-ld // print("non-ld"); //make triangle pqu tris[tristoreplace[0]*6+0] = p; tris[tristoreplace[0]*6+1] = q; tris[tristoreplace[0]*6+2] = u; //make triangle pqv tris[tristoreplace[1]*6+0] = p; tris[tristoreplace[1]*6+1] = q; tris[tristoreplace[1]*6+2] = v; // setColor(255,255,255); // drawLine(xpoints[p],ypoints[p],xpoints[q],ypoints[q]); //erase the neighbor data for neighboring triangles for each new one erasea = trisborderingtri(tristoreplace[0]); eraseb = trisborderingtri(tristoreplace[1]); erase = Array.concat(erasea,eraseb); for (i=0;i<6;i++) { if (erase[i] != -1) { tris[erase[i]*6+3] = -1; tris[erase[i]*6+4] = -1; tris[erase[i]*6+5] = -1; } } //regenerate neighbor data updatetrineighbors(tristoreplace[0], 1); //make a list neighboring edges to possibly add to the edgestack sortedup = newArray(u,p); sortedup = Array.sort(sortedup); sortedpv = newArray(p,v); sortedpv = Array.sort(sortedpv); sortedvq = newArray(v,q); sortedvq = Array.sort(sortedvq); sortedqu = newArray(q,u); sortedqu = Array.sort(sortedqu); edgestoldtest = Array.concat(sortedup,sortedpv,sortedvq,sortedqu); for (i=0;i<8;i=i+2) { //test those edges - add to the edgestack if they aren't on the border of the convex hull j=trisborderingedge(edgestoldtest[i],edgestoldtest[i+1]); // Array.print(j); if (j[1]!=-1) { //e.g. if this edge isn't on the border of the convex hull edgetoadd = newArray(edgestoldtest[i],edgestoldtest[i+1]); // Array.print(edgetoadd); adduniqueedges(edgetoadd); } } // Array.print(edgestack); } else { //if already ld // setColor(255,255,255); // drawLine(xpoints[u],ypoints[u],xpoints[v],ypoints[v]); } // waitForUser("x"); } //start the triangulation setBatchMode(true); var xpoints = newArray(); var ypoints = newArray(); edges = newArray(); //edges to be stored as two point ids in succession var tris = newArray(); //three point ids in succession followed by three tri ids (that border edges) in succession or -1 if no border. var borderpoly = newArray(); //point ids on the boundary of the expanding triangulation, in connecting order var edgestack = newArray(); //two point ids for edges to be checked for ld at each step Roi.getCoordinates(xpoints, ypoints); n=lengthOf(ypoints); if (n < 3) { print('<3 points, exiting.'); exit; } xranks = Array.rankPositions(xpoints); ysorted = newArray(n); //sort x directly Array.sort(xpoints); //sort ypoints by x coordinates for (p=0;p 0) { edgeflipifnotld(edgestack[0],edgestack[1]); // print(edgestack.length/2); } } } //end per-point processing } //edgeflip again while (edgestack.length > 0) { edgeflipifnotld(edgestack[0],edgestack[1]); // print(edgestack.length/2); } // now calculate the alpha shape //functions for doing this: function compareTri(tri,alpha) { //return 1 if entire tri is in the alpha shape twoalphasquared = alpha * alpha * 4; tri = tri * 6; //tri id to coordinates adx = (xpoints[tris[tri+1]] - xpoints[tris[tri]]); ady = (ypoints[tris[tri+1]] - ypoints[tris[tri]]); sideasquared = adx*adx + ady*ady; bdx = (xpoints[tris[tri+2]] - xpoints[tris[tri+1]]); bdy = (ypoints[tris[tri+2]] - ypoints[tris[tri+1]]); sidebsquared = bdx*bdx + bdy*bdy; cdx = (xpoints[tris[tri]] - xpoints[tris[tri+2]]); cdy = (ypoints[tris[tri]] - ypoints[tris[tri+2]]); sidecsquared = cdx*cdx + cdy*cdy; if (twoalphasquared <= sideasquared || twoalphasquared <= sidebsquared || twoalphasquared <= sidecsquared) { return 0; } else { return 1;} } function compareEdge(u,v,alpha) { //return 1 if edge is in the alpha shape twoalphasquared = alpha * alpha * 4; dx = (xpoints[u] - xpoints[v]); dy = (ypoints[u] - ypoints[v]); edgesquared = dx*dx + dy*dy; if (twoalphasquared <= edgesquared) { return 0; } else { return 1;} } function alphaedgeunique(n) {//count of the edge specified (by coord of first point) in edgesinalpha? //returns 1 if unique or 0 if not unique count = 0; u = edgesinalpha[n]; v = edgesinalpha[n+1]; for (i=0;i