def get_total_length(self): """ return total length of all edges """ for _, _, data in self.edges_iter(data=True): print curves.curve_length(data['curve']) return sum( curves.curve_length(data['curve']) for _, _, data in self.edges_iter(data=True))
def simplify(self, epsilon=0): """ remove nodes with degree=2 and simplifies the curves describing the edges if epsilon is larger than 0 """ # remove nodes with degree=2 while True: for n, d in self.degree_iter(): if d == 2: try: n1, n2 = self.neighbors(n) except ValueError: # this can happen for a self-loop, where n1 == n2 continue # get the points points1 = self.get_single_edge_data(n, n1)['curve'] points2 = self.get_single_edge_data(n, n2)['curve'] # remove the node and the edges self.remove_edges_from([(n, n1), (n, n2)]) self.remove_node(n) # add the new edge points = curves.merge_curves(points1, points2) self.add_edge_line(n1, n2, points) break else: break #< no changes => we're done! # simplify the curves describing the edges if epsilon > 0: for _, _, data in self.edges_iter(data=True): data['curve'] = curves.simplify_curve(data['curve'], epsilon) data['length'] = curves.curve_length(data['curve'])
def add_edge_line(self, n1, n2, curve): """ adds an edge to the graph """ curve = np.asarray(curve) p1, p2 = curve[0], curve[-1] coords1, coords2 = self.node[n1]["coords"], self.node[n2]["coords"] if np.allclose(p1, coords1) and np.allclose(p2, coords2): data = {"curve": curve, "length": curves.curve_length(curve)} elif np.allclose(p1, coords2) and np.allclose(p2, coords1): data = {"curve": curve[::-1], "length": curves.curve_length(curve)} else: raise ValueError("The curve given by `curve` does not connect the " "specified nodes.") if data["length"] > 1e-6: self.add_edge(n1, n2, attr_dict=data)
def add_edge_line(self, n1, n2, curve): """ adds an edge to the graph """ curve = np.asarray(curve) p1, p2 = curve[0], curve[-1] coords1, coords2 = self.node[n1]['coords'], self.node[n2]['coords'] if (np.allclose(p1, coords1) and np.allclose(p2, coords2)): data = {'curve': curve, 'length': curves.curve_length(curve)} elif (np.allclose(p1, coords2) and np.allclose(p2, coords1)): data = {'curve': curve[::-1], 'length': curves.curve_length(curve)} else: raise ValueError('The curve given by `curve` does not connect the ' 'specified nodes.') if data['length'] > 1e-6: self.add_edge(n1, n2, attr_dict=data)
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 get_total_length(self): """ return total length of all edges """ for _, _, data in self.edges_iter(data=True): print curves.curve_length(data['curve']) return sum(curves.curve_length(data['curve']) for _, _, data in self.edges_iter(data=True))
def get_centerline_estimate(self, end_points=None): """ determines an estimate to a center line of the polygon `end_points` can either be None, a single Point, or two points. """ import regions #< lazy import to prevent circular dependencies def _find_point_connection(p1, p2=None, maximize_distance=False): """ estimate centerline between the one or two points """ mask, offset = self.get_mask(margin=2, dtype=np.int32, ret_offset=True) p1 = (p1[0] - offset[0], p1[1] - offset[1]) if maximize_distance or p2 is None: dist_prev = 0 if maximize_distance else np.inf # iterate until second point is found while True: # make distance map starting from point p1 distance_map = mask.copy() regions.make_distance_map(distance_map, start_points=(p1, )) # find point farthest point away from p1 idx_max = np.unravel_index(distance_map.argmax(), distance_map.shape) dist = distance_map[idx_max] p2 = idx_max[1], idx_max[0] if dist <= dist_prev: break dist_prev = dist # take farthest point as new start point p1 = p2 else: # locate the centerline between the two given points p2 = (p2[0] - offset[0], p2[1] - offset[1]) distance_map = mask regions.make_distance_map(distance_map, start_points=(p1, ), end_points=(p2, )) # find path between p1 and p2 path = regions.shortest_path_in_distance_map(distance_map, p2) return curves.translate_points(path, *offset) if end_points is None: # determine both end points path = _find_point_connection(np.array(self.position), maximize_distance=True) else: end_points = np.squeeze(end_points) if end_points.shape == (2, ): # determine one end point path = _find_point_connection(end_points, maximize_distance=False) elif end_points.shape == (2, 2): # both end points are already determined path = _find_point_connection(end_points[0], end_points[1]) elif end_points.ndim == 2 and end_points.shape[1] == 2: # multiple end points found => we have to find the end points # that are farthest apart longest_path, length = None, 0 for k_1, p_1 in enumerate(end_points): for p_2 in end_points[:k_1]: path = _find_point_connection(p_1, p_2) path_len = curves.curve_length(path) if path_len > length: longest_path, length = path, path_len path = longest_path else: raise TypeError('`end_points` must have shape (2,) or (n, 2), ' 'but we found %s' % str(end_points.shape)) return path
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
def get_centerline_estimate(self, end_points=None): """ determines an estimate to a center line of the polygon `end_points` can either be None, a single point, or two points. """ import regions #< lazy import to prevent circular dependencies def _find_point_connection(p1, p2=None, maximize_distance=False): """ estimate centerline between the one or two points """ mask, offset = self.get_mask(margin=2, dtype=np.int32, ret_offset=True) p1 = (p1[0] - offset[0], p1[1] - offset[1]) if maximize_distance or p2 is None: dist_prev = 0 if maximize_distance else np.inf # iterate until second point is found while True: # make distance map starting from point p1 distance_map = mask.copy() regions.make_distance_map(distance_map, start_points=(p1,)) # find point farthest point away from p1 idx_max = np.unravel_index(distance_map.argmax(), distance_map.shape) dist = distance_map[idx_max] p2 = idx_max[1], idx_max[0] if dist <= dist_prev: break dist_prev = dist # take farthest point as new start point p1 = p2 else: # locate the centerline between the two given points p2 = (p2[0] - offset[0], p2[1] - offset[1]) distance_map = mask regions.make_distance_map(distance_map, start_points=(p1,), end_points=(p2,)) # find path between p1 and p2 path = regions.shortest_path_in_distance_map(distance_map, p2) return curves.translate_points(path, *offset) if end_points is None: # determine both end points path = _find_point_connection(np.array(self.position), maximize_distance=True) else: end_points = np.squeeze(end_points) if end_points.shape == (2, ): # determine one end point path = _find_point_connection(end_points, maximize_distance=False) elif end_points.shape == (2, 2): # both end points are already determined path = _find_point_connection(end_points[0], end_points[1]) elif end_points.ndim == 2 and end_points.shape[1] == 2: # multiple end points found => we have to find the end points # that are farthest apart longest_path, length = None, 0 for k_1, p_1 in enumerate(end_points): for p_2 in end_points[:k_1]: path = _find_point_connection(p_1, p_2) path_len = curves.curve_length(path) if path_len > length: longest_path, length = path, path_len path = longest_path else: raise TypeError('`end_points` must have shape (2,) or (n, 2), ' 'but we found %s' % str(end_points.shape)) return path