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
Exemple #2
0
 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)
Exemple #5
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])
        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, :]
Exemple #7
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