def centerline(self): """ determine the center line of the tail """ mask, offset = self.mask dist_map = cv2.distanceTransform(mask, cv2.DIST_L2, 5) # setup active contour algorithm ac = ActiveContour( blur_radius=self.centerline_blur_radius, closed_loop=False, alpha=0, #< line length is constraint by beta beta=self.centerline_bending_stiffness, gamma=self.centerline_adaptation_rate) ac.max_iterations = self.centerline_max_iterations ac.set_potential(dist_map) # find centerline starting from the ventral_side points = curves.translate_points(self.ventral_side, -offset[0], -offset[1]) spacing = self.centerline_spacing points = curves.make_curve_equidistant(points, spacing=spacing) # use the active contour algorithm points = ac.find_contour(points) points = curves.make_curve_equidistant(points, spacing=spacing) # translate points back into global coordinate system points = curves.translate_points(points, offset[0], offset[1]) # orient the centerline such that it starts at the posterior end dist1 = curves.point_distance(points[0], self.endpoints[0]) dist2 = curves.point_distance(points[-1], self.endpoints[0]) if dist1 > dist2: points = points[::-1] return points
def centerline(self): """ determine the center line of the tail """ mask, offset = self.mask dist_map = cv2.distanceTransform(mask, cv2.DIST_L2, 5) # setup active contour algorithm ac = ActiveContour(blur_radius=self.centerline_blur_radius, closed_loop=False, alpha=0, #< line length is constraint by beta beta=self.centerline_bending_stiffness, gamma=self.centerline_adaptation_rate) ac.max_iterations = self.centerline_max_iterations ac.set_potential(dist_map) # find centerline starting from the ventral_side points = curves.translate_points(self.ventral_side, -offset[0], -offset[1]) spacing = self.centerline_spacing points = curves.make_curve_equidistant(points, spacing=spacing) # use the active contour algorithm points = ac.find_contour(points) points = curves.make_curve_equidistant(points, spacing=spacing) # translate points back into global coordinate system points = curves.translate_points(points, offset[0], offset[1]) # orient the centerline such that it starts at the posterior end dist1 = curves.point_distance(points[0], self.endpoints[0]) dist2 = curves.point_distance(points[-1], self.endpoints[0]) if dist1 > dist2: points = points[::-1] return points
def _search_predug_in_region(self, region, reflect=False): """ searches the predug using a template """ slice_x, slice_y = region.slices # determine the image in the region, only considering under ground img = self.image[slice_y, slice_x].astype(np.uint8) # load the file that describes the template data filename = self.params['predug/template_file'] path = os.path.join(os.path.dirname(__file__), '..', 'assets', filename) if not os.path.isfile(path): logging.warn('Predug template file `%s` was not found', path) return None with open(path, 'r') as infile: yaml_content = yaml.load(infile) if not yaml_content: logging.warn('Predug template file `%s` is empty', path) return None # load the template image img_file = yaml_content['image'] coords = np.array(yaml_content['coordinates'], np.double) path = os.path.join(os.path.dirname(__file__), '..', 'assets', img_file) # read the image from the file as grey scale template = cv2.imread(path)[:, :, 0] if self.params['predug/scale_predug']: # get the scaled size of the template image target_size = (int(self.params['predug/template_width']), int(self.params['predug/template_height'])) # scale the predug coordinates to match the template size coords[:, 0] *= target_size[0] / template.shape[1] #< x-coordinates coords[:, 1] *= target_size[1] / template.shape[0] #< y-coordinates # scale the template image itself template = cv2.resize(template, target_size) if reflect: # reflect the image on the vertical center line template = cv2.flip(template, 1) template_width = template.shape[1] coords[:, 0] = template_width - coords[:, 0] - 1 # do the template matching # res = cv2.matchTemplate(img, template, cv2.TM_SQDIFF_NORMED) # val_best, _, loc_best, _ = cv2.minMaxLoc(res) res = cv2.matchTemplate(img, template, cv2.TM_CCOEFF_NORMED) _, val_best, _, loc_best = cv2.minMaxLoc(res) # determine the rough outline of the predug in the region coords = curves.translate_points(coords, *loc_best) # determine the outline of the predug in the video coords = curves.translate_points(coords, region.x, region.y) return val_best, shapes.Polygon(coords)
def _refine_predug(self, candidate): """ uses the color information directly to specify the predug """ # determine a bounding rectangle region = candidate.bounds size = max(region.width, region.height) region.buffer(0.5 * size) # < increase by 50% in each direction # extract the region from the image slice_x, slice_y = region.slices img = self.image[slice_y, slice_x].astype(np.uint8, copy=True) # build the estimate polygon poly_p = affinity.translate(candidate.polygon, -region.x, -region.y) poly_s = affinity.translate(self.ground.get_sky_polygon(), -region.x, -region.y) def fill_mask(color, margin=0): """ fills the mask with the buffered regions """ for poly in (poly_p, poly_s): pts = np.array(poly.buffer(margin).boundary.coords, np.int32) cv2.fillPoly(mask, [pts], color) # prepare the mask for the grabCut algorithm burrow_width = self.params["burrows/width"] mask = np.full_like(img, cv2.GC_BGD, dtype=np.uint8) # < sure background fill_mask(cv2.GC_PR_BGD, 0.25 * burrow_width) # < possible background fill_mask(cv2.GC_PR_FGD, 0) # < possible foreground fill_mask(cv2.GC_FGD, -0.25 * burrow_width) # < sure foreground # run GrabCut algorithm img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB) bgdmodel = np.zeros((1, 65), np.float64) fgdmodel = np.zeros((1, 65), np.float64) try: cv2.grabCut(img, mask, (0, 0, 1, 1), bgdmodel, fgdmodel, 2, cv2.GC_INIT_WITH_MASK) except: # any error in the GrabCut algorithm makes the whole function useless logging.warn("GrabCut algorithm failed for predug") return candidate # turn the sky into background pts = np.array(poly_s.boundary.coords, np.int32) cv2.fillPoly(mask, [pts], cv2.GC_BGD) # extract a binary mask determining the predug predug_mask = (mask == cv2.GC_FGD) | (mask == cv2.GC_PR_FGD) predug_mask = predug_mask.astype(np.uint8) # simplify the mask using binary operations w = int(0.5 * burrow_width) kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (w, w)) predug_mask = cv2.morphologyEx(predug_mask, cv2.MORPH_OPEN, kernel) # extract the outline of the predug contour = regions.get_contour_from_largest_region(predug_mask) # translate curves back into global coordinate system contour = curves.translate_points(contour, region.x, region.y) return shapes.Polygon(contour)
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