def get_centerline_optimized(self, alpha=1e3, beta=1e6, gamma=0.01, spacing=20, max_iterations=1000, endpoints=None): """ determines the center line of the polygon using an active contour algorithm """ # use an active contour algorithm to find centerline ac = ActiveContour(blur_radius=1, alpha=alpha, beta=beta, gamma=gamma, closed_loop=False) ac.max_iterations = max_iterations # set the potential from the distance map mask, offset = self.get_mask(1, ret_offset=True) potential = cv2.distanceTransform(mask, cv2.DIST_L2, 5) ac.set_potential(potential) # initialize the centerline from the estimate points = self.get_centerline_estimate(endpoints) points = curves.make_curve_equidistant(points, spacing=spacing) points = curves.translate_points(points, -offset[0], -offset[1]) # anchor the end points anchor = np.zeros(len(points), np.bool) anchor[0] = anchor[-1] = True # find the best contour points = ac.find_contour(points, anchor, anchor) points = curves.make_curve_equidistant(points, spacing=spacing) return curves.translate_points(points, *offset)
def get_centerline_smoothed(self, points=None, spacing=10, skip_length=90, **kwargs): """ determines the center line of the polygon using an active contour algorithm. If `points` are given, they are used for getting the smoothed centerline. Otherwise, we determine the optimized centerline influenced by the additional keyword arguments. `skip_length` is the length that is skipped at either end of the center line when the smoothed variant is calculated """ if points is None: points = self.get_centerline_optimized(spacing=spacing, **kwargs) length = curves.curve_length(points) # get the points to interpolate points = curves.make_curve_equidistant(points, spacing=spacing) skip_points = int(skip_length / spacing) points = points[skip_points:-skip_points] # do spline fitting to smooth the line smoothing_condition = length spline_degree = 3 try: tck, _ = interpolate.splprep(np.transpose(points), k=spline_degree, s=smoothing_condition) except (ValueError, TypeError): # do not interpolate if there are problems pass else: # extend the center line in both directions to make sure that it # crosses the outline overshoot = 5 * skip_length #< absolute overshoot num_points = (length + 2 * overshoot) / spacing overshoot /= length #< overshoot relative to total length s = np.linspace(-overshoot, 1 + overshoot, num_points) points = interpolate.splev(s, tck) points = zip(*points) #< transpose list # restrict center line to polygon shape cline = geometry.LineString(points).intersection(self.polygon) if isinstance(cline, geometry.MultiLineString): points = max(cline, key=lambda obj: obj.length).coords else: points = np.array(cline.coords) return points
def get_centerline_smoothed(self, points=None, spacing=10, skip_length=90, **kwargs): """ determines the center line of the polygon using an active contour algorithm. If `points` are given, they are used for getting the smoothed centerline. Otherwise, we determine the optimized centerline influenced by the additional keyword arguments. `skip_length` is the length that is skipped at either end of the center line when the smoothed variant is calculated """ if points is None: points = self.get_centerline_optimized(spacing=spacing, **kwargs) # get properties of the line length = curves.curve_length(points) endpoints = points[0], points[-1] # get the points to interpolate points = curves.make_curve_equidistant(points, spacing=spacing) skip_points = int(skip_length / spacing) points = points[skip_points:-skip_points] # do spline fitting to smooth the line smoothing_condition = length spline_degree = 3 try: tck, _ = interpolate.splprep(np.transpose(points), k=spline_degree, s=smoothing_condition) except (ValueError, TypeError): # do not interpolate if there are problems pass else: # extend the center line in both directions to make sure that it # crosses the outline overshoot = 20*skip_length #< absolute overshoot num_points = (length + 2*overshoot)/spacing overshoot /= length #< overshoot relative to total length s = np.linspace(-overshoot, 1 + overshoot, num_points) points = interpolate.splev(s, tck) points = zip(*points) #< transpose list # restrict center line to the section between the end points dists = spatial.distance.cdist(endpoints, points) ks = sorted(np.argmin(dists, axis=1)) cline = geometry.LineString(points[ks[0] : ks[1]+1]) if isinstance(cline, geometry.MultiLineString): points = max(cline, key=lambda obj: obj.length).coords else: points = np.array(cline.coords) return points
def find_contour(self, curve, anchor_x=None, anchor_y=None): """ adapts the contour given by points to the potential image anchor_x can be a list of indices for those points whose x-coordinate should be kept fixed. anchor_y is the respective argument for the y-coordinate """ if self.fx is None: raise RuntimeError('Potential must be set before the contour can ' 'be adapted.') # curve must be equidistant for this implementation to work curve = np.asarray(curve) points = curves.make_curve_equidistant(curve) # check for marginal small cases if len(points) <= 2: return points def _get_anchors(indices, coord): """ helper function for determining the anchor points """ if indices is None or len(indices) == 0: return tuple(), tuple() # get points where the coordinate `coord` has to be kept fixed ps = curve[indices, :] # find the points closest to the anchor points dist = spatial.distance.cdist(points, ps) return np.argmin(dist, axis=0), ps[:, coord] # determine anchor_points if requested if anchor_x is not None or anchor_y is not None: has_anchors = True x_idx, x_vals = _get_anchors(anchor_x, 0) y_idx, y_vals = _get_anchors(anchor_y, 1) else: has_anchors = False # determine point spacing if it is not given ds = curves.curve_length(points) / (len(points) - 1) # try loading the evolution matrix from the cache cache_key = (len(points), ds) Pinv = self._Pinv_cache.get(cache_key, None) if Pinv is None: # add new item to cache Pinv = self.get_evolution_matrix(len(points), ds) self._Pinv_cache[cache_key] = Pinv # restrict control points to shape of the potential points[:, 0] = np.clip(points[:, 0], 0, self.fx.shape[1] - 2) points[:, 1] = np.clip(points[:, 1], 0, self.fx.shape[0] - 2) # create intermediate array points_initial = points.copy() ps = points.copy() for k in xrange(self.max_iterations): # calculate external force fex = image.subpixels(self.fx, points) fey = image.subpixels(self.fy, points) # move control points ps[:, 0] = np.dot(Pinv, points[:, 0] + self.gamma * fex) ps[:, 1] = np.dot(Pinv, points[:, 1] + self.gamma * fey) # enforce the position of the anchor points if has_anchors: ps[x_idx, 0] = x_vals ps[y_idx, 1] = y_vals # check the distance that we evolved residual = np.abs(ps - points).sum() # restrict control points to shape of the potential points[:, 0] = np.clip(ps[:, 0], 0, self.fx.shape[1] - 2) points[:, 1] = np.clip(ps[:, 1], 0, self.fx.shape[0] - 2) if residual < self.residual_tolerance * self.gamma: break # collect additional information self.info['iteration_count'] = k + 1 self.info['total_variation'] = np.abs(points_initial - points).sum() return points
def find_contour(self, curve, anchor_x=None, anchor_y=None): """ adapts the contour given by points to the potential image anchor_x can be a list of indices for those points whose x-coordinate should be kept fixed. anchor_y is the respective argument for the y-coordinate """ if self.fx is None: raise RuntimeError('Potential must be set before the contour can ' 'be adapted.') # curve must be equidistant for this implementation to work curve = np.asarray(curve) points = curves.make_curve_equidistant(curve) # check for marginal small cases if len(points) <= 2: return points def _get_anchors(indices, coord): """ helper function for determining the anchor points """ if indices is None or len(indices) == 0: return tuple(), tuple() # get points where the coordinate `coord` has to be kept fixed ps = curve[indices, :] # find the points closest to the anchor points dist = spatial.distance.cdist(points, ps) return np.argmin(dist, axis=0), ps[:, coord] # determine anchor_points if requested if anchor_x is not None or anchor_y is not None: has_anchors = True x_idx, x_vals = _get_anchors(anchor_x, 0) y_idx, y_vals = _get_anchors(anchor_y, 1) else: has_anchors = False # determine point spacing if it is not given ds = curves.curve_length(points)/(len(points) - 1) # try loading the evolution matrix from the cache cache_key = (len(points), ds) Pinv = self._Pinv_cache.get(cache_key, None) if Pinv is None: # add new item to cache Pinv = self.get_evolution_matrix(len(points), ds) self._Pinv_cache[cache_key] = Pinv # restrict control points to shape of the potential points[:, 0] = np.clip(points[:, 0], 0, self.fx.shape[1] - 2) points[:, 1] = np.clip(points[:, 1], 0, self.fx.shape[0] - 2) # create intermediate array points_initial = points.copy() ps = points.copy() for k in xrange(self.max_iterations): # calculate external force fex = image.subpixels(self.fx, points) fey = image.subpixels(self.fy, points) # move control points ps[:, 0] = np.dot(Pinv, points[:, 0] + self.gamma*fex) ps[:, 1] = np.dot(Pinv, points[:, 1] + self.gamma*fey) # enforce the position of the anchor points if has_anchors: ps[x_idx, 0] = x_vals ps[y_idx, 1] = y_vals # check the distance that we evolved residual = np.abs(ps - points).sum() # restrict control points to shape of the potential points[:, 0] = np.clip(ps[:, 0], 0, self.fx.shape[1] - 2) points[:, 1] = np.clip(ps[:, 1], 0, self.fx.shape[0] - 2) if residual < self.residual_tolerance * self.gamma: break # collect additional information self.info['iteration_count'] = k + 1 self.info['total_variation'] = np.abs(points_initial - points).sum() return points