def __init__(self): # Init visualization fig = vv.figure(102); vv.clf() a = vv.gca() # Init points pp = Pointset(2) pp.append(14,12) pp.append(12,16) pp.append(16,16) self._pp = vv.plot(pp, ls='', ms='.', mw=10, mc='g') # Init line representing the circle self._cc = vv.plot(pp, lc='r', lw=2, ms='.', mw=5, mew=0, mc='r') # Set limits a.SetLimits((0,32), (0,32)) a.daspectAuto = False # Init object being moved self._movedPoint = None # Enable callbacks self._pp.hitTest = True self._pp.eventMouseDown.Bind(self.OnDown) self._pp.eventMouseUp.Bind(self.OnUp) a.eventMotion.Bind(self.OnMotion) a.eventDoubleClick.Bind(self.OnDD) # Start self.Apply()
def Apply(self): # Create big list of points pp = Pointset(2) for y in range(self._patchSize): for x in range(self._patchSize): pp.append(x,y) # Get point locations c = Point( self._c._points[0].x, self._c._points[0].y ) p1 = Point( self._p1._points[0].x, self._p1._points[0].y ) p2 = Point( self._p2._points[0].x, self._p2._points[0].y ) # Get masks M1, M2 = chopstick_criteria(c, p1, p2, pp) M1.shape = self._patchSize, self._patchSize M2.shape = self._patchSize, self._patchSize # Update image self._im[:] = 9 self._im[M1] -= 3 self._im[M2] -= 3 self._t.Refresh() # Draw self._c.Draw()
def Apply(self): # Get 2D pointset pp = Pointset(2) for p in self._pp._points: pp.append(p.x, p.y) # Fit circle and sample and show c = fit_cirlce(pp) cc = sample_circle(c, 32) self._cc.SetPoints(cc) # Draw self._cc.Draw()
def check_for_nodes(connecting_points, slice_i_plus_1): """ Given a set of stent points, the algorithm checks whether there are any duplicates. A duplicate would mean that that point was found by two stent points and could therefore be a node. An additional check is done to check whether there are no connecting points above the potential node. If this is the case, than the point is not a node. """ #Pointset to store 2D nodes point_nodes = Pointset(2) #Pointset to store point which need to be removed point_remove = Pointset(2) #points in slice_i_plus_1 points_check = stentPoints2d.detect_points(slice_i_plus_1) #For every connecting point for i in range(len(connecting_points)): for j in range(len(connecting_points)): #if points are the same if connecting_points[i].Distance( connecting_points[j]) <= 3 and i != j: #check if this point would find a connecting point point = Pointset(2) point.Append(connecting_points[i]) node_check, unused = find_connecting_points( point, points_check) #Only add node once. if not node_check: if not point_nodes: point_nodes.Append( (connecting_points[i] + connecting_points[j]) / 2) elif point_nodes.Contains(connecting_points[i]) == False: point_nodes.Append( (connecting_points[i] + connecting_points[j]) / 2) #The connecting points need to be removed later point_remove.append(connecting_points[i]) point_remove.append(connecting_points[i]) return point_nodes, point_remove
def detect_points2(slice, y_x, spacing, width=10): """ Alternative version of detect_points, which uses a reference point. Used for measurements on our phantom. """ # create slice as double (makes a copy) slice = slice.astype(np.float64) # create new point set to store points pp = Pointset(2) th_minHU = 300 refy, refx = y_x refy -= spacing # because we start with incrementing it while 1: # next refy += spacing if refy > slice.shape[0]: break # get patch y1, y2 = refy - spacing//4, refy + spacing//4 x1, x2 = refx - width//2, refx + width//2 if y1<0: y1=0 patch = slice[y1:y2+1, x1:x2+1] # detect Iy, Ix = np.where( (patch == patch.max()) & (patch > th_minHU) ) try: Ix = Ix[0] Iy = Iy[0] except IndexError: continue # if no points found... y, x = y1+Iy, x1+Ix if y<=0 or y>=slice.shape[0]-1: continue # get subpixel and store patch2 = slice[y-1:y+2,x-1:x+2] dx,dy = subpixel.fitLQ2_5(patch2) pp.append( x+dx, y+dy ) return pp
def cluster_points(pp, c, pauseTime=0, showConvergeToo=True): """ cluster_points(pp, c, pauseTime=0) Given a set of points, and the centreline position, this function returns a set of points, which is a subset of pp. The returned pointset is empty on error. This algorithm uses the chopstick clustering implementation. The first step is the "stick" method. We take the closest point from the centre and attach one end of a stick to it. The stick has the length of the radius of the fitted circle. We rotate the stick counterclockwise untill it hits a point, or untill we rotate too far without finding a point, thus failing. When it fails, we try again with a slighly larger stick. This is to close gaps of up to almost 100 degrees. When we reach a point were we've already been, we stop. Afterwards, the set of points is sorted by angle. In the second step we try to add points: we pick two subsequent points and check what other points are closer than "stick" to both these points, where "stick" is the distance between the points. We will break this stick in two and take the best point in the cluster, thus the name: chopstick. BUT ONLY if it is good enough! We draw two lines from the points under consideration to the centre. The angle that these two lines make is a reference for the allowed angle that the two lines may make that run from the two points to the new point. To be precise: ang > (180 - refang) - offset offset is a parameter to control the strictness. The third part of this step consists of removing points. We will only remove points that are closer to the centre than both neighbours. We check each point, comparing it with its two neighbours on each side, applying the same criterion as above. This will remove outliers that lie withing the stent, such as points found due to the contrast fluid... The latter two parts are repeated untill the set of points does not change. Each time the centre is recalculated by fitting a circle. """ # Get better center if showConvergeToo: c, I = converge_to_centre(pp, c, pauseTime=pauseTime) else: c, I = converge_to_centre(pp, c) if not I: return Pointset(2) # Init list with vis objects (to be able to delete them) showObjects = [] if pauseTime: fig = vv.gcf() # Short names pi = np.pi # Minimum and maximum angle that the stick is allowed to make with the line # to the circle-centre. Pretty intuitive... # it is a relative measure, we will multiply it with a ratio: # radius/distance_current_point_to_radius. This allows ellipses to be # segmented, while remaining strict for circles. difAng1_p = 0.0*pi difAng2_p = 0.7*pi ## Step 1, stick method to find an initial set of points # Select start point (3th point returned by converge_to_centre) icurr = I[2] # Init list L, at the beginning only contains icurr L = [icurr] # Largest amount of iterations that makes sense. Probably the loop # exits sooner. maxIter = len(pp) # Enter loop for iter in range(maxIter): # We can think of it as fixing the stick at one end at the current # point and then rotating it in a direction such that a point is # found in the clockwise direction of the current point. We thus # need the angle between the next and current point to be not much # more than the angle between the current point and the circle # cenrre + 90 deg. But it must be larger than the angle between the # current point and the circle centre. # Calculate distances dists = pp.distance(pp[icurr]) # Do the next bit using increasing stick length, untill success for stick in [c.r*1.0, c.r*1.5, c.r*2.0]: # Find subset of points that be "reached by the stick" Is, = np.where(dists<stick) # Calculate angle with circle centre refAng = c.angle2(pp[icurr]) # Culcuate angles with points that are in reach of the stick angs = pp[Is].angle2(pp[icurr]) # Select the points that are in the proper direction # There are TWO PARAMETERS HERE (one important) the second TH can # not be 0.5, because we allow an ellipse. # Use distance measure to make sure not to select the point itself. difAngs = subtract_angles(angs, refAng) difAng2 = difAng2_p # pp[icurr].distance(c) / c.r # Set current too really weid value icurr2, = np.where(Is==icurr) if len(icurr2): difAngs[icurr2] = 99999.0 # Select. If a selection, we're good! II, = np.where( (difAngs > difAng1_p) + (difAngs < difAng2)) if len(II): break else: # No success _objectClearer(showObjects) return Pointset(2) # Select the point with the smallest angle tmp = difAngs[II] inext, = np.where(tmp == tmp.min()) # inext is index in subset. Make it apply to global set inext = Is[ II[ inext[0] ] ] inext = int(inext) # Show if pauseTime>0: # Delete _objectClearer(showObjects) # Show center ob1 = vv.plot(c, ls='', ms='x', mc='r', mw=10, axesAdjust=0) # Show all points ob2 = vv.plot(pp, ls='', ms='.', mc='g', mw=6, axesAdjust=0) # Show selected points L ob3 = vv.plot(pp[L], ls='', ms='.', mc='y', mw=10, axesAdjust=0) # Show next ob4 = vv.plot(pp[inext], ls='', ms='.', mc='r', mw=12, axesAdjust=0) # Show stick vec = ( pp[inext]-pp[icurr] ).normalize() tmp = Pointset(2) tmp.append(pp[icurr]); tmp.append(pp[icurr]+vec*stick) ob5 = vv.plot(tmp, lw=2, lc='b') # Store objects and wait showObjects = [ob1,ob2,ob3,ob4,ob5] fig.DrawNow() time.sleep(pauseTime) # Check whether we completed a full round already if inext in L: break else: L.append(inext) # Prepare for next round icurr = inext # Sort the list by the angles tmp = zip( pp[L].angle2(c), L ) tmp.sort(key=lambda x:x[0]) L = [i[1] for i in tmp] # Clear visualization _objectClearer(showObjects) ## Step 2 and 3, chopstick algorithm to find more points and discard outliers # Init L = [int(i) for i in L] # Make Python ints Lp = [] round = 0 # Iterate ... while Lp != L and round < 20: round += 1 #print 'round', round # Clear list (but store previous) Lp = [i for i in L] L = [] # We need at least three points if len(Lp)<3: _objectClearer(showObjects) print('oops: len(LP)<3' ) return [] # Recalculate circle c = fit_cirlce(pp[Lp], False) if c.r == 0.0: print('oops: c.r==0' ) _objectClearer(showObjects) return [] # Step2: ADD POINTS for iter in range(len(Lp)): # Current point icurr = Lp[iter] if iter < len(Lp)-1: inext = Lp[iter+1] else: inext = Lp[0] # Prepare, get p1 and p2 p1 = pp[icurr] p2 = pp[inext] # Apply masks to points in pp M1, M2 = chopstick_criteria(c, p1, p2, pp) # Combine measures. I is now the subset (of p) of OK points I, = np.where(M1*M2) if not len(I): L.append(icurr) continue elif len(I)==1: ibetw = int(I) else: # Multiple candidates: find best match pptemp = pp[I] dists = p1.distance(pptemp) + p2.distance(pptemp) II, = np.where( dists==dists.min() ) ibetw = int( I[II[0]] ) # Add point L.append(icurr) if not ibetw in L: L.append(ibetw) # Check assert ibetw not in [icurr, inext] # Draw if pauseTime>0: # Delete _objectClearer(showObjects) # Show center ob1 = vv.plot(c, ls='', ms='x', mc='r', mw=10, axesAdjust=0) # Show all points ob2 = vv.plot(pp, ls='', ms='.', mc='g', mw=6, axesAdjust=0) # Show selected points L ob3 = vv.plot(pp[L], ls='', ms='.', mc='y', mw=10, axesAdjust=0) # Show between and vectors ob4 = vv.plot(pp[ibetw], ls='', ms='.', mc='r', mw=12, axesAdjust=0) ob5 = vv.plot(pp[[icurr, ibetw, inext]], ls='-', lc='g', axesAdjust=0) ob6 = vv.plot(pp[[icurr, inext]], ls=':', lc='g', axesAdjust=0) # Store objects and wait showObjects = [ob1,ob2,ob3,ob4,ob5,ob6] fig.DrawNow() time.sleep(pauseTime) # Lpp stores the set of points we have untill now, we will refill the # set L, maybe with less points Lpp = [int(i) for i in L] L = [] # Step3: REMOVE POINTS for iter in range(len(Lpp)): # Current point and neighbours ibetw = Lpp[iter] if iter<len(Lpp)-1: inext = Lpp[iter+1] else: inext = Lpp[0] if iter>0: icurr = Lpp[iter-1] else: icurr = Lpp[-1] # Test # print icurr, ibetw, inext assert ibetw not in [icurr, inext] # Prepare, get p1 and p2 and p3 p1 = pp[icurr] p2 = pp[inext] p3 = pp[ibetw] # Apply masks to points in pp M1, M2 = chopstick_criteria(c, p1, p2, p3) M = M1*M2 # Do we keep the point? if M.sum(): L.append(ibetw) # Draw if pauseTime>0 and not M.sum(): # Delete _objectClearer(showObjects) # Show center ob1 = vv.plot(c, ls='', ms='x', mc='r', mw=10, axesAdjust=0) # Show all points ob2 = vv.plot(pp, ls='', ms='.', mc='g', mw=6, axesAdjust=0) # Show selected points L ob3 = vv.plot(pp[L], ls='', ms='.', mc='y', mw=10, axesAdjust=0) # Show between and vectors ob4 = vv.plot(pp[ibetw], ls='', ms='.', mc='r', mw=12, axesAdjust=0) ob5 = vv.plot(pp[[icurr, ibetw, inext]], ls='-', lc='r', axesAdjust=0) ob6 = vv.plot(pp[[icurr, inext]], ls='-', lc='g', axesAdjust=0) # Store objects and wait showObjects = [ob1,ob2,ob3,ob4,ob5,ob6] fig.DrawNow() time.sleep(pauseTime) # Done if round == 20: print('Warning: chopstick seemed not to converge.') _objectClearer(showObjects) #print 'cluster end', len(L) return pp[L]
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
def detect_points(slice, th_gc=2000, th_minHU=300, sigma=1.6): """ Detect points Detects points on a stent in the given slice. Slice should be a numpy array. - The Gaussian Curvature should be above a threshold (needle detection) (th_gc) - An absolute (weak) threshold is used based on the Houndsfield units (th_minHU) - Streak artifacts are suppressed - sigma is the used scale at which the GC is calculated. """ # Make sure that the slice is a float if slice.dtype not in [np.float32, np.float64]: slice = slice.astype(np.float32) # Create slice to supress streak artifacts # Where streak artifacts are present, the image is inverted, anywhere else # its zero. By also calculating derivatives on this image and than adding # it, the threshold will never be reached where the streak artifacts are. sliceStreak = th_streHU - slice sliceStreak[sliceStreak<0] = 0 # Create new point set to store points pp = Pointset(2) # Calculate Gaussian curvature if True: Lxx = gaussfun.gfilter(slice, sigma, [0,2]) Ltmp = gaussfun.gfilter(sliceStreak, sigma, [0,2]) Lxx = Lxx+2*Ltmp Lxx[Lxx>0]=0; Lyy = gaussfun.gfilter(slice, sigma, [2,0]) Ltmp = gaussfun.gfilter(sliceStreak, sigma, [2,0]) Lyy = Lyy+2*Ltmp Lyy[Lyy>0]=0; Lgc = Lxx * Lyy # Make a smoothed version slice_smoothed = gaussfun.gfilter(slice, 0.5, 0) # Make a selection of candidate pixels Iy,Ix = np.where( (slice > th_minHU) & (Lgc > th_gc) ) # Mask to detect clashes clashMask = np.zeros(slice.shape, dtype=np.bool) # Detect local maxima for x,y in zip(Ix,Iy): if x==0 or y==0 or x==slice.shape[1]-1 or y==slice.shape[0]-1: continue # Select patch patch1 = slice[y-1:y+2,x-1:x+2] patch2 = slice_smoothed[y-1:y+2,x-1:x+2] if slice[y,x] == patch1.max():# and slice_smoothed[y,x] == patch2.max(): # Found local max (allowing shared max) # Not if next to another found point if clashMask[y,x]: continue # Not a streak artifact if patch2.min() <= th_streHU: continue # Subpixel #dx,dy = subpixel.fitLQ2_5(patch1) # Store pp.append( x, y ) clashMask[y-1:y+2,x-1:x+2] = 1 # Express points in world coordinates and return if isinstance(slice, Aarray): ori = [i for i in reversed(slice.origin)] sam = [i for i in reversed(slice.sampling)] pp *= Point(sam) pp += Point(ori) return pp
# Fit circle and sample and show c = fit_cirlce(pp) cc = sample_circle(c, 32) self._cc.SetPoints(cc) # Draw self._cc.Draw() ## Tests if __name__ == "__main__": import visvis as vv tester1 = ChopstickCriteriaTester() # tester2 = FitCircleTester() if False: pp = Pointset(2) pp.append(1,1) pp.append(10,3) pp.append(8,8) pp.append(2,6) pp.append(1,2) fig = vv.figure(103) fig.Clear() a = vv.gca() a.daspectAuto = False fig.position = -799.00, 368.00, 544.00, 382.00 vv.plot(pp, ls='', ms='.', mc='g') c2 = converge_to_centre(pp, Point(6,5), 5, pauseTime=0.3)