def fit_cirlce(pp, warnIfIllDefined=True): """ fit_cirlce(pp, warnIfIllDefined=True) Calculate the circle (x - c.x)**2 + (y - x.y)**2 = c.r**2 From the set of points pp. Returns a point instance with an added attribute "r" specifying the radius. In case the three points are on a line, the algorithm will fail, and return 0 for x,y and r. This waring can be suppressed. The solution is a Least Squares fit. The method as describes in [1] is called Modified Least Squares (MLS) and poses a closed form solution which is very robust. [1] Dale Umbach and Kerry N. Jones 2000 A Few Methods for Fitting Circles to Data IEEE Transactions on Instrumentation and Measurement """ # Init error point ce = Point(0,0) ce.r = 0.0 def cov(a, b): n = len(a) Ex = a.sum() / n Ey = b.sum() / n return ( (a-Ex)*(b-Ey) ).sum() / (n-1) # Get x and y elements X = pp[:,0] Y = pp[:,1] # In the paper there is a factor n*(n-1) in all equations below. However, # this factor is removed by devision in the equations in the following cell A = cov(X,X) B = cov(X,Y) C = cov(Y,Y) D = 0.5 * ( cov(X,Y**2) + cov(X,X**2) ) E = 0.5 * ( cov(Y,X**2) + cov(Y,Y**2) ) # Calculate denumerator denum = A*C - B*B if denum==0: if warnIfIllDefined: print("Warning: can not fit a circle to the given points.") return ce # Calculate point c = Point( (D*C-B*E)/denum, (A*E-B*D)/denum ) # Calculate radius c.r = c.distance(pp).sum() / len(pp) # Done return c
def get_slice(vol, sampling, center_point, global_direction, look_for_bifurcation, alpha): """ Given two center points and the direction of the stent, the algorithm will first create a slice on the second center point. A new center point is calculated on this slice, which is used to find a local direction. The local direction is used to update the global direction with weigthing factor alpha (where 0 is following the local direction and 1 the global direction). Using this new direction a slice is created on the first center point. """ vec1 = Point(1, 0, 0) vec2 = Point(0, 1, 0) #Set second center point as rotation point rotation_point = Point(center_point[2], center_point[1], center_point[0]) + global_direction #Create the slice using the global direction slice, vec1, vec2 = slice_from_volume(vol, sampling, global_direction, rotation_point, vec1, vec2) #Search for points in this slice and filter these with the clustering method points_in_slice = stentPoints2d.detect_points(slice) try: points_in_slice_filtered = stentPoints2d.cluster_points(points_in_slice, \ Point(128,128)) except AssertionError: points_in_slice_filtered = [] #if filtering did not succeed the unfiltered points are used if not points_in_slice_filtered: points_in_slice_filtered = points_in_slice succeeded = False else: succeeded = True #if looking for bifurcation than the radius has to be calculated if succeeded and look_for_bifurcation: center_point_with_radius = stentPoints2d.fit_cirlce( \ points_in_slice_filtered) #convert 2d center point to 3d for the event that the bifurcation #is found center_point_3d_with_radius = convert_2d_point_to_3d_point( \ Point(rotation_point[2], rotation_point[1], rotation_point[0]), vec1, vec2, center_point_with_radius) #Convert pointset into point center_point_3d_with_radius = Point(center_point_3d_with_radius[0]) #Give the 3d center point the radius as attribute center_point_3d_with_radius.r = center_point_with_radius.r else: center_point_3d_with_radius = Point(0, 0, 0) center_point_3d_with_radius.r = [] #Now the local direction of the stent has to be found. Note that it is not #done when the chopstick filtering did not succeed. if succeeded: better_center_point = stentPoints2d.converge_to_centre(points_in_slice_filtered, \ Point(128,128)) else: better_center_point = Point(0, 0), [] #If no better center point has been found, there is no need to update global #direction if better_center_point[0] == Point(0, 0): updated_direction = global_direction else: better_center_point_3d = convert_2d_point_to_3d_point(\ Point(rotation_point[2], rotation_point[1], rotation_point[0]), \ vec1, vec2, better_center_point[0]) #Change pointset into a point better_center_point_3d = Point(better_center_point_3d[0]) #local direction (from starting center point to the better center point) local_direction = Point(better_center_point_3d[2]-center_point[2], \ better_center_point_3d[1]-center_point[1], better_center_point_3d[0] \ -center_point[0]).Normalize() #calculate updated direction updated_direction = ((1 - alpha) * local_direction + alpha * global_direction).Normalize() #Now a new slice can be created using the first center point as rotation point. rotation_point = Point(center_point[2], center_point[1], center_point[0]) slice, vec1, vec2 = slice_from_volume(vol, sampling, updated_direction, rotation_point, vec1, vec2) #Change order rotation_point = Point(center_point[0], center_point[1], center_point[2]) #return new center point new_center_point = center_point + Point(updated_direction[2], \ updated_direction[1], updated_direction[0]) return slice, new_center_point, updated_direction, rotation_point, vec1, vec2, \ center_point_3d_with_radius
def converge_to_centre(pp, c, nDirections=5, maxRadius=50, pauseTime=0): """ converge_to_centre(pp, c) Given a set of points and an initial center point c, will find a better estimate of the center. Returns (c, L), with L indices in pp that were uses to fit the final circle. """ # Shorter names N = nDirections pi = np.pi # Init point to return on error ce = Point(0,0) ce.r = 0 # Are there enough points? if len(pp) < 3: return ce, [] # Init a pointset of centers we've has so far cc = Pointset(2) # Init list with vis objects (to be able to delete them) showObjects = [] if pauseTime: fig = vv.gcf() while c not in cc: # Add previous center cc.append(c) # Calculate distances and angles dists = c.distance(pp) angs = c.angle2(pp) # Get index of closest points i, = np.where(dists==dists.min()) i = iClosest = int(i[0]) # Offset the angles with the angle relative to the closest point. refAng = angs[i] angs = subtract_angles(angs, refAng) # Init L, the indices to the closest point in each direction L = [] # Get closest point on N directions for angle in [float(angnr)/N*2*pi for angnr in range(N)]: # Get indices of points that are in this direction dangs = subtract_angles(angs, angle) I, = np.where(np.abs(dangs) < pi/N ) # Select closest if len(I): distSelection = dists[I] minDist = distSelection.min() J, = np.where(distSelection==minDist) if len(J) and minDist < maxRadius: L.append( int(I[J[0]]) ) # Check if ok if len(L) < 3: return ce, [] # Remove spurious points (points much furter away that the 3 closest) distSelection = dists[L] tmp = [d for d in distSelection] d3 = sorted(tmp)[2] # Get distance of 3th closest point I, = np.where(distSelection < d3*2) L = [L[i] for i in I] # Select points ppSelect = Pointset(2) for i in L: ppSelect.append(pp[i]) # Refit circle cnew = fit_cirlce(ppSelect,False) if cnew.r==0: return ce, [] # Show if pauseTime>0: # Delete for ob in showObjects: ob.Destroy() # Plot center and new center ob1 = vv.plot(c, ls='', ms='x', mc='r', mw=10, axesAdjust=0) ob2 = vv.plot(cnew, ls='', ms='x', mc='r', mw=10, mew=0, axesAdjust=0) # Plot selection points ob3 = vv.plot(ppSelect, ls='', ms='.', mc='y', mw=10, axesAdjust=0) # Plot lines dividing the directions tmpSet1 = Pointset(2) tmpSet2 = Pointset(2) for angle in [float(angnr)/N*2*pi for angnr in range(N)]: angle = -subtract_angles(angle, refAng) dx, dy = np.cos(angle), np.sin(angle) tmpSet1.append(c.x, c.y) tmpSet1.append(c.x+dx*d3*2, c.y+dy*d3*2) for angle in [float(angnr+0.5)/N*2*pi for angnr in range(N)]: angle = -subtract_angles(angle, refAng) dx, dy = np.cos(angle), np.sin(angle) tmpSet2.append(c.x, c.y) tmpSet2.append(c.x+dx*d3*2, c.y+dy*d3*2) ob4 = vv.plot(tmpSet1, lc='y', ls='--', axesAdjust=0) ob5 = vv.plot(tmpSet2, lc='y', lw=3, axesAdjust=0) # Store objects and wait showObjects = [ob1,ob2,ob3,ob4,ob5] fig.DrawNow() time.sleep(pauseTime) # Use new c = cnew # Done for ob in showObjects: ob.Destroy() return c, L