FilFinder icon indicating copy to clipboard operation
FilFinder copied to clipboard

Ignoring looped filaments

Open researcherys opened this issue 4 years ago • 1 comments

There are 2 kinds of problems with looped filaments introduced in filfinder2D:

  1. Looped filament without any end gives: UnboundLocalError: local variable 'max_path_length' referenced before assignment;
  2. Looped filament with any end attached, even insignificant by length(at figure), is ignored, when providing longest path: the longest path of the insignificant branch is provided instead of longer looped filbranch. Prob2

How to avoid losing information contained with loops when, e.g., extracting longest paths?

OS: Linux, Ubuntu 18.04 Environment: Pycharm 2020.3.3 PlantCV Version 3.11.0]

researcherys avatar Feb 08 '21 19:02 researcherys

I'm working on this currently. To deal with the first max_path_length error resulting from looped filaments, I added a check before running "analyze_skeletons":

# define two functions to identify junctions and endpoints:

def find_junctions(skel):
    """Finds pixels with exactly three neighbors."""
    kernel = np.array([
        [1, 1, 1],
        [1, 10, 1],
        [1, 1, 1]
    ])

    neighbors_count = convolve(skel.astype(int), kernel, mode='constant', cval=0)
    return (neighbors_count - 10 == 3) & skel

def find_endpoints(skel):
    # Define a kernel that counts the number of neighbors
    kernel = np.array([
        [1, 1, 1],
        [1, 10, 1],
        [1, 1, 1]
    ])

    neighbors_count = convolve(skel.astype(int), kernel, mode='constant', cval=0)
    # Endpoints are skeleton points with only one neighbor
    return (neighbors_count == 11) & skel

def make_skeletons(binary_image):
    """
    This function uses the FilFinder package to find and prune skeletons of images.
    Args:
        image: 2D numpy array or list of points that make up polygon mask
    Returns:
        skeleton: 2D numpy array with skeletons
        skeleton_longpath: 2D numpy array with skeletons that have been pruned to a single line
    """
   
    # Load in skeleton class for skeleton pruning
    fil = FilFinder2D(binary_image, beamwidth=0 * u.pix, mask=binary_image)
    
    # This is a necessary step for the fil object. It does nothing.
    fil.preprocess_image(skip_flatten=True)

    # This makes the skeleton
    fil.medskel(verbose=False)

    # find junctions and endpoints
    junctions = np.asarray(np.nonzero(find_junctions(fil.skeleton))).shape[1]
    endpoints = np.asarray(np.nonzero(find_endpoints(fil.skeleton))).shape[1]
    print(f'Number of junctions is {junctions}')
    print(f'Number of endpoints is {endpoints}')
    print('-----------------------------------------------')

    if junctions == 0 and endpoints == 0:
        return fil.skeleton, fil.skeleton
    else:    
        # This prunes the skeleton
        fil.analyze_skeletons(branch_thresh=BRANCH_THRESH * u.pix, prune_criteria='length',
                            skel_thresh=MIN_CAP_LEN * u.pix)    
        return fil.skeleton, fil.skeleton_longpath

A circle with have zero endpoints and zero junctions, so if I don't find them then I don't run the pruning algorithm and just return the circle.

gt8mar avatar Aug 28 '23 22:08 gt8mar