def calculate_burrow_centerline(self, burrow, point_start=None): """ determine the centerline of a burrow with one exit """ if point_start is None: point_start = burrow.centerline[0] # get a binary image of the burrow mask, shift = burrow.get_mask(margin=2, dtype=np.int32, ret_offset=True) # move starting point onto ground line ground_line = self.ground.linestring point_start = curves.get_projection_point(ground_line, point_start) point_start = (int(point_start[0]) - shift[0], int(point_start[1]) - shift[1]) mask[point_start[1], point_start[0]] = 1 # calculate the distance from the start point regions.make_distance_map(mask, [point_start]) # find the second point by locating the farthest point _, _, _, p_end = cv2.minMaxLoc(mask) # find an estimate for the centerline from the shortest distance from # the end point to the burrow exit points = regions.shortest_path_in_distance_map(mask, p_end) # translate the points back to global coordinates centerline = curves.translate_points(points, shift[0], shift[1]) # save centerline such that burrow exit is first point centerline = centerline[::-1] # add points that might be outside of the burrow contour ground_start = curves.get_projection_point(ground_line, centerline[0]) centerline.insert(0, ground_start) # simplify the curve centerline = cv2.approxPolyDP(np.array(centerline, np.int), epsilon=1, closed=False) # save the centerline in the burrow structure burrow.centerline = centerline[:, 0, :]
def calculate_burrow_centerline(self, burrow, point_start=None): """ determine the centerline of a burrow with one exit """ if point_start is None: point_start = burrow.centerline[0] # get a binary image of the burrow mask, shift = burrow.get_mask(margin=2, dtype=np.int32, ret_offset=True) # move starting point onto ground line ground_line = self.ground.linestring point_start = curves.get_projection_point(ground_line, point_start) point_start = (int(point_start[0]) - shift[0], int(point_start[1]) - shift[1]) mask[point_start[1], point_start[0]] = 1 # calculate the distance from the start point regions.make_distance_map(mask, [point_start]) # find the second point by locating the farthest point _, _, _, p_end = cv2.minMaxLoc(mask) # find an estimate for the centerline from the shortest distance from # the end point to the burrow exit points = regions.shortest_path_in_distance_map(mask, p_end) # translate the points back to global coordinates centerline = curves.translate_points(points, shift[0], shift[1]) # save centerline such that burrow exit is first point centerline = centerline[::-1] # add points that might be outside of the burrow contour ground_start = curves.get_projection_point(ground_line, centerline[0]) if isinstance(centerline, np.ndarray): centerline = np.insert(centerline, 0, ground_start).reshape(-1, 2) else: centerline.insert(0, ground_start) # simplify the curve centerline = cv2.approxPolyDP(np.array(centerline, np.int), epsilon=1, closed=False) # save the centerline in the burrow structure burrow.centerline = centerline[:, 0, :]
def _get_burrow_centerline(self, burrow, points_start, points_end=None): """ determine the centerline of a burrow with one exit """ ground_line = self.ground.linestring # get a binary image of the burrow mask, shift = burrow.get_mask(margin=2, dtype=np.int32, ret_offset=True) # mark the start points according to their distance to the ground line # dists_g = [ground_line.distance(geometry.Point(p)) # for p in points_start] points_start = curves.translate_points(points_start, -shift[0], -shift[1]) for p in points_start: mask[p[1], p[0]] = 1 if points_end is None: # end point is not given and will thus be determined automatically # calculate the distance from the start point regions.make_distance_map(mask.T, points_start) # find the second point by locating the farthest point _, _, _, p_end = cv2.minMaxLoc(mask) else: # prepare the end point if present # translate that point to the mask _frame points_end = curves.translate_points(points_end, -shift[0], -shift[1]) for p in points_end: mask[p[1], p[0]] = 1 # calculate the distance from the start point regions.make_distance_map(mask.T, points_start, points_end) # get the distance between the start and the end point dists = [mask[p[1], p[0]] for p in points_end] best_endpoint = np.argmin(dists) p_end = points_end[best_endpoint] # find an estimate for the centerline from the shortest distance from # the end point to the burrow exit points = regions.shortest_path_in_distance_map(mask, p_end) # debug.show_shape(geometry.MultiPoint(points_start), # geometry.Point(p_end), # background=mask) # exit() # translate the points back to global coordinates centerline = curves.translate_points(points, shift[0], shift[1]) # save centerline such that burrow exit is first point centerline = centerline[::-1] # add points that might be outside of the burrow contour ground_start = curves.get_projection_point(ground_line, centerline[0]) centerline.insert(0, ground_start) if points_end is not None: ground_end = curves.get_projection_point(ground_line, centerline[-1]) centerline.append(ground_end) # simplify the curve centerline = cv2.approxPolyDP(np.array(centerline, np.int), epsilon=1, closed=False) # save the centerline in the burrow structure burrow.centerline = centerline[:, 0, :]
def calculate_burrow_properties(self, burrow, ground_line=None): """ calculates additional properties of the burrow """ # load some parameters burrow_width = self.params['burrow/width_typical'] min_length = self.params['burrow/branch_length_min'] # determine the burrow end points endpoints = burrow.get_endpoints(ground_line) # get distance map from centerline distance_map, offset = burrow.get_mask(margin=2, dtype=np.uint16, ret_offset=True) cline = burrow.centerline start_points = curves.translate_points(cline, -offset[0], -offset[1]) regions.make_distance_map(distance_map, start_points) # determine endpoints, which are not already part of the centerline end_coords = np.array([ep.coords for ep in endpoints]) dists = spatial.distance.cdist(end_coords, cline) extra_ends = end_coords[dists.min(axis=1) > burrow_width, :].tolist() extra_ends = curves.translate_points(extra_ends, -offset[0], -offset[1]) # get additional points that are far away from the centerline map_max = ndimage.filters.maximum_filter(distance_map, burrow_width) map_maxima = (distance_map == map_max) & (distance_map > min_length) maxima = np.array(np.nonzero(map_maxima)).T # determine the object from which we measure the distance to the sky if ground_line is not None: outside = ground_line.linestring else: outside = geometry.MultiPoint(end_coords) # define a helper function for checking the connection to the ground burrow_poly = burrow.polygon.buffer(2) def _direct_conn_to_ground(point, has_offset=False): """ helper function checking the connection to the ground """ if has_offset: point = (point[0] + offset[0], point[1] + offset[1]) p_ground = curves.get_projection_point(outside, point) conn_line = geometry.LineString([point, p_ground]) return conn_line.length < 1 or conn_line.within(burrow_poly) branch_points = [] branch_point_separation = self.params['burrow/branch_point_separation'] if maxima.size > 0: if len(maxima) == 1: clusters = [0] else: # cluster maxima to reduce them to single end points # this is important when a burrow has multiple exits to the ground clusters = cluster.hierarchy.fclusterdata( maxima, branch_point_separation, method='single', criterion='distance' ) cluster_ids = np.unique(clusters) logging.debug('Found %d possible branch point(s)' % len(cluster_ids)) # find the additional point from the clusters for cluster_id in cluster_ids: candidates = maxima[clusters == cluster_id, :] # get point with maximal distance from center line dists = [distance_map[x, y] for x, y in candidates] y, x = candidates[np.argmax(dists)] # check whether this point is close to an endpoint point = geometry.Point(x + offset[0], y + offset[1]) branch_depth = point.distance(outside) if (branch_depth > min_length or not _direct_conn_to_ground((x, y), has_offset=True)): branch_points.append((x, y)) # save some output for debugging self._debug['possible_branches'] = \ curves.translate_points(branch_points, offset[0], offset[1]) # find the burrow branches burrow.branches = [] if extra_ends or branch_points: num_branches = len(extra_ends) + len(branch_points) logging.info('Found %d possible branch(es)' % num_branches) # create generator for iterating over all additional points gen = ((p_class, p_coords) for p_class, points in enumerate([extra_ends, branch_points]) for p_coords in points) # connect all additional points to the centerline -> branches for ep_id, ep in gen: line = regions.shortest_path_in_distance_map(distance_map, ep) line = curves.translate_points(line, offset[0], offset[1]) # estimate the depth of the branch depth = max(geometry.Point(p).distance(outside) for p in (line[0], line[-1])) # check whether the line corresponds to an open branch # open branches are branches where all connection lines between # the branch and ground line are fully contained in the burrow # polygon if ep_id == 1 and depth < min_length: num_direct = sum(1 for p_branch in line if _direct_conn_to_ground(p_branch)) ratio_direct = num_direct / len(line) if ratio_direct > 0.75: # the ground is directly reachable from most points line = None if line: burrow.branches.append(line) if ground_line: self._add_burrow_angle_statistics(burrow, ground_line) return # filter branches to make sure that they are not all connected to ground # determine the morphological graph graph = burrow.get_morphological_graph() # combine multiple branches at exits and remove short branches that are # not exits exit_nodes = [] for points_exit in burrow.get_exit_regions(ground_line): exit_line = geometry.LineString(points_exit) # find the graph nodes that are close to the exit graph_nodes = [] for node, data in graph.nodes(data=True): node_point = geometry.Point(data['coords']) if exit_line.distance(node_point) < 3: graph_nodes.append((node, node_point)) # find the graph node that is closest to the exit point exit_point = exit_line.interpolate(0.5, normalized=True) dists = [exit_point.distance(node_point) for _, node_point in graph_nodes] exit_id = np.argmin(dists) for node_id, (node, _) in enumerate(graph_nodes): if node_id == exit_id: # save the exit node for later use exit_nodes.append(node) else: # remove additional graph nodes graph.remove_node(node) # remove short branches that are not exit nodes length_min = self.params['burrow/branch_length_min'] graph.remove_short_edges(length_min=length_min, exclude_nodes=exit_nodes) # remove nodes of degree two graph.simplify() burrow.morphological_graph = graph