Exemplo n.º 1
0
def refine_worm(image, initial_area, candidate_edges):
    # find strong worm edges (roughly equivalent to the edges found by find_initial_worm,
    # which are in candidate_edges): smooth the image, do canny edge-finding, and
    # then keep only those edges near candidate_edges
    smooth_image = restoration.denoise_tv_bregman(image, 140).astype(numpy.float32)
    smoothed, gradient, sobel = canny.prepare_canny(smooth_image, 8, initial_area)
    local_maxima = canny.canny_local_maxima(gradient, sobel)
    candidate_edge_region = ndimage.binary_dilation(candidate_edges, iterations=4)
    strong_edges = local_maxima & candidate_edge_region

    # Now threshold the image to find dark blobs as our initial worm region
    # First, find areas in the initial region unlikely to be worm pixels
    mean, std = mcd.robust_mean_std(smooth_image[initial_area][::4], 0.85)
    non_worm = (smooth_image > mean - std) & initial_area
    # now fit a smoothly varying polynomial to the non-worm pixels in the initial
    # region of interest, and subtract that from the actual image to generate
    # an image with a flat illumination field
    background = polyfit.fit_polynomial(smooth_image, mask=non_worm, degree=2)
    minus_bg = smooth_image - background
    # now recalculate a threshold from the background-subtracted pixels
    mean, std = mcd.robust_mean_std(minus_bg[initial_area][::4], 0.85)
    initial_worm = (minus_bg < mean - std) & initial_area
    # Add any pixels near the strong edges to our candidate worm position
    initial_worm |= ndimage.binary_dilation(strong_edges, iterations=3)
    initial_worm = mask.fill_small_radius_holes(initial_worm, 5)

    # Now grow/shrink the initial_worm region so that as many of the strong
    # edges from the canny filter are in contact with the region edges as possible.
    ac = active_contour.EdgeClaimingAdvection(initial_worm, strong_edges,
        max_region_mask=initial_area)
    stopper = active_contour.StoppingCondition(ac, max_iterations=100)
    while stopper.should_continue():
        ac.advect(iters=1)
        ac.smooth(iters=1, depth=2)
    worm_mask = mask.fill_small_radius_holes(ac.mask, 7)

    # Now, get edges from the image at a finer scale
    smoothed, gradient, sobel = canny.prepare_canny(smooth_image, 0.3, initial_area)
    local_maxima = canny.canny_local_maxima(gradient, sobel)
    strong_sum = strong_edges.sum()
    highp = 100 * (1 - 1.5*strong_sum/local_maxima.sum())
    lowp = max(100 * (1 - 3*strong_sum/local_maxima.sum()), 0)
    low_worm, high_worm = numpy.percentile(gradient[local_maxima], [lowp, highp])
    fine_edges = canny.canny_hysteresis(local_maxima, gradient, low_worm, high_worm)

    # Expand out the identified worm area to include any of these finer edges
    closed_edges = ndimage.binary_closing(fine_edges, structure=S)
    worm = ndimage.binary_propagation(worm_mask, mask=worm_mask|closed_edges, structure=S)
    worm = ndimage.binary_closing(worm, structure=S, iterations=2)
    worm = mask.fill_small_radius_holes(worm, 5)
    worm = ndimage.binary_opening(worm)
    worm = mask.get_largest_object(worm)
    # Last, smooth the shape a bit to reduce sharp corners, but not too much to
    # sand off the tail
    ac = active_contour.CurvatureMorphology(worm, max_region_mask=initial_area)
    ac.smooth(depth=2, iters=2)
    return strong_edges, ac.mask
Exemplo n.º 2
0
def find_initial_worm(small_image, well_mask):
    # plan here is to find known good worm edges with Canny using a stringent threshold, then
    # relax the threshold in the vicinity of the good edges.
    # back off another pixel from the well edge to avoid gradient from the edge
    shrunk_mask = ndimage.binary_erosion(well_mask, structure=S)
    smoothed, gradient, sobel = canny.prepare_canny(small_image, 2, shrunk_mask)
    local_maxima = canny.canny_local_maxima(gradient, sobel)
    # Calculate stringent and medium-stringent thresholds. The stringent threshold
    # is the 200th-brightest edge pixel, and the medium is the 450th-brightest pixel
    highp = 100 * (1-200/local_maxima.sum())
    highp = max(highp, 94)
    mediump = 100 * (1-450/local_maxima.sum())
    mediump = max(mediump, 94)
    low_worm, medium_worm, high_worm = numpy.percentile(gradient[local_maxima], [94, mediump, highp])
    stringent_worm = canny.canny_hysteresis(local_maxima, gradient, low_worm, high_worm)
    # Expand out 20 pixels from the stringent worm edges to make our search space
    stringent_area = ndimage.binary_dilation(stringent_worm, mask=well_mask, iterations=20)
    # now use the relaxed threshold but only in the stringent area
    relaxed_worm = canny.canny_hysteresis(local_maxima, gradient, low_worm, medium_worm) & stringent_area
    # join very close-by objects, and remove remaining small objects
    candidate_worm = ndimage.binary_dilation(relaxed_worm, structure=S)
    candidate_worm = ndimage.binary_erosion(candidate_worm)
    candidate_worm = mask.remove_small_area_objects(candidate_worm, 30, structure=S)
    # Now figure out the biggest blob of nearby edges, and call that the worm region
    glommed_candidate = ndimage.binary_dilation(candidate_worm, structure=S, iterations=2)
    glommed_candidate = ndimage.binary_erosion(glommed_candidate, iterations=2)
    # get just outline, not any regions filled-in due to closing
    glommed_candidate ^= ndimage.binary_erosion(glommed_candidate)
    glommed_candidate = mask.get_largest_object(glommed_candidate, structure=S)
    worm_area = ndimage.binary_dilation(glommed_candidate, mask=well_mask, structure=S, iterations=12)
    worm_area = mask.fill_small_radius_holes(worm_area, max_radius=15)
    candidate_edges = relaxed_worm & candidate_worm & worm_area
    return candidate_edges, worm_area
Exemplo n.º 3
0
def get_well_mask(image):
    small_image = pyramid.pyr_down(image, 4)
    smoothed, gradient, sobel = canny.prepare_canny(small_image, 2)
    local_maxima = canny.canny_local_maxima(gradient, sobel)
    # well outline has ~6000 px full-size = 1500 px at 4x downsampled
    # So find the intensity value of the 2000th-brightest pixel, via percentile:
    highp = 100 * (1-2000/local_maxima.sum())
    highp = max(highp, 90)
    low_edge, high_edge = numpy.percentile(gradient[local_maxima], [90, highp])
    # Do canny edge-finding starting with gradient pixels as bright or brighter
    # than the 2000th-brightest pixel, and spread out to the 90th percentile
    # intensity:
    well_edge = canny.canny_hysteresis(local_maxima, gradient, low_edge, high_edge)
    # connect nearby edges and remove small unconnected bits
    well_edge = ndimage.binary_closing(well_edge, structure=S)
    well_edge = mask.remove_small_area_objects(well_edge, 300, structure=S)
    # Get map of distances and directions to nearest edge to use for contour-fitting
    distances, nearest_edge = active_contour.edge_direction(well_edge)
    # initial curve is the whole image less one pixel on the outside
    initial = numpy.ones(well_edge.shape, dtype=bool)
    initial[:,[0,-1]] = 0
    initial[[0,-1],:] = 0
    # Now evolve the curve inward until it contacts the canny well edges.
    gac = active_contour.GAC(initial, nearest_edge, advection_mask=(distances < 10), balloon_direction=-1)
    stopper = active_contour.StoppingCondition(gac, max_iterations=200)
    while stopper.should_continue():
        # otherwise evolve the curve by shrinking, advecting toward edges, and smoothing
        gac.balloon_force(iters=3)
        gac.advect(iters=2)
        gac.smooth()
    gac.smooth(depth=3)
    # now erode everywhere the contour edge is right on a canny edge:
    gac.move_to_outside(well_edge[tuple(gac.inside_border_indices.T)])
    gac.smooth()
    well_mask = gac.mask
    if well_mask.sum() / well_mask.size < 0.25:
        # if the well mask is too small, something went very wrong
        well_mask = None
    return small_image, well_mask
Exemplo n.º 4
0
def find_centerline_pixels(dv_coords,
                           mask,
                           sigma=1,
                           worm_width=50,
                           low_threshold=0.25,
                           high_threshold=0.6):
    """ Find pixels along the centerline of the worm given a DV-coordinate image.

    Parameters:
        dv_coords: floating-point image of dorsal-ventral worm coordinates with
            midline values high and edge values low. Should NOT be masked.
        mask: segmented mask (approximate is ok, as ridgeline may extend out of
            the masked region if the dv_coords image supports it.
        sigma: gaussian blur applied to smooth the dv_coords image
        worm_width: approximate width of worms at their widest at this magnification.
            50 is a reasonable value for adult worms at 5x magnification.
        low_threshold: lowest dv-coordinate value that could possibly be part of the
            centerline.
        high_threshold: lowest dv-coordinate value that would almost certainly be
            part of the centerline.

    Returns: all_ridges, extended_ridges, neighboring_ridges
        all_ridges: all local ridglines in the image, regardless of mask. Mainly
            useful for diagnostics.
        extended_ridges: all low-confidence-or-better ridgelines extending from
            high-confidence regions within the mask. Strictest set of ridges.
        neighboring_ridges: all low-confidence-or-better ridgelines extending from
            locations within three pixels of the extended_ridges. Most inclusive
            set of ridges.
    """

    # A horizontal ridge of dv_coords will have a positive y gradient on the low-y side of the ridge
    # and a negative gradient on the other, with a zero-gradient patch right along the center of the
    # ridgeline:
    # y^
    #  |
    #  |-------- horizontal ridgeline of high-value dv-coordinates on a low-value background
    #  |
    #  --------->x
    #
    # Gradient in y direction:
    # y^
    #  |--------
    #  |00000000
    #  |++++++++
    #  --------->x
    #
    # A vertical ridge will have a positive x gradient on the low-x side and negative on the other.
    # A diagonal ridge will be a combination of the above, and the orientation of the diagonal
    # can be determined by whether the low-x and low-y sides of the ridge are the same.
    # I.e. if the ridge is +45 degrees, the low-x and low-y sides are opposite:
    #                    x-gradient:       y-gradient:
    #  y^               y^                y^
    #   |  / ridge       |++0-             |--0+
    #   | /              |+0--             |-0++
    #   |/               |0---             |0+++
    #   ------>x         ------>x          ------>x
    # In the above the low-x side of the diagonal ridgeline is above the diagonal and the
    # low-y side is below. So in this case, the x and y gradients on each side of the ridge
    # are of opposite sign.
    # However, if the ridge is -45 degrees, the low-x and low-y sides are both below
    # the ridge, and so the gradients have the same sign.
    #                    x-gradient:       y-gradient:
    #   ------>x         ------>x          ------>x
    #   |\               |0---             |0---
    #   | \              |+0--             |+0--
    #   |  \ ridge       |++0-             |++0-
    #  yv               yv                yv
    #
    # To find the ridgeline pixels (0 gradient surrounded by high + and - gradients on
    # either side), we could look for maxima of the second derivative,
    # but that is noise-prone in practice. Instead, we will take a local average of
    # the absolute value of the x- and y-gradients nearby each pixel. The 0-valued ridge
    # pixels will have high averages. This, however, discards the orientation information.
    # To recover the degree to which the y-gradient has an + or - diagonal angle,
    # we compare the local average of the product of the x and y gradients to
    # the local average of the product of the absolute values of the gradients.
    # This number ranges from -1 indicating that the x- and y-gradients are on
    # opposite sides of the ridgeline, to +1 indicating that they're on the same sides.
    # We then weight the local magnitude in the y direction by this number, to produce
    # an (x, y) vector that captures the direction normal to the ridge.
    #
    # Given these normals, we can then locate the exact center pixels of the ridge
    # by choosing pixels from the original dv_coordinate image that are local maxima
    # along the ridge normal. Even if the ridgeline itself is slowly climbing or
    # descending, the saddle points along the ridge will be maximal normal to the
    # ridge. This technique comes from the Canny edge-finding method.
    #
    # Finally, we also use Canny hysteresis thresholding to extend an initial seed
    # of high-confidence ridge pixels into potentially lower confidence regions:
    # If a low-confidence region of ridge is connected to a high-confidence region
    # (or in this implementation, within a few pixels therof), extend the ridge
    # into it.
    smooth_dv = ndimage.gaussian_filter(dv_coords, sigma)
    ridge = smooth_dv**3
    dx, dy = numpy.gradient(ridge)
    adx, ady = numpy.abs(dx), numpy.abs(dy)
    # average over a region about the worm's width, so choose a smoothing
    # gaussian with a standard deviation about 1/4 of the width, and truncate
    # at two standard deivations (1/2th of the width) on either side of the ridge
    gradient_sigma = worm_width / 4
    local_magnitude_x = ndimage.gaussian_filter(adx,
                                                gradient_sigma,
                                                truncate=2)
    local_magnitude_y = ndimage.gaussian_filter(ady,
                                                gradient_sigma,
                                                truncate=2)
    orientation = ndimage.gaussian_filter(dx * dy, gradient_sigma, truncate=2)
    max_orientation = ndimage.gaussian_filter(adx * ady,
                                              gradient_sigma,
                                              truncate=2)
    orientation /= max_orientation
    all_ridges = canny.canny_local_maxima(
        ridge, [local_magnitude_x, local_magnitude_y * orientation])
    # Hysteresis threshold: high_mask below is regions of the local_maxima image
    # that are within the overall mask region and also have a dv_coord above
    # the high-confidence value. Then we extend those regions into any connected
    # low-confidence regions (the low_mask regions), ignoring low-confidence
    # regions not contiguous with a high-confidence region.
    high_mask = mask & all_ridges & (smooth_dv >= high_threshold)
    low_mask = all_ridges & (smooth_dv >= low_threshold)
    extended_ridges = ndimage.binary_propagation(high_mask,
                                                 mask=low_mask,
                                                 structure=FULLY_CONNECTED)
    # Now add regions of the maxima that are just a couple pixels away from the
    # extended regions too.
    # nearby_pixels below is regions of the low_mask that are both within the overall
    # mask and near to regions in extended_ridges. Then expand out such pixels to
    # all connected pixels within low_mask
    nearby_pixels = mask & low_mask & ndimage.binary_dilation(
        extended_ridges, iterations=3, structure=FULLY_CONNECTED)
    neighboring_ridges = ndimage.binary_propagation(nearby_pixels,
                                                    mask=low_mask,
                                                    structure=FULLY_CONNECTED)
    neighboring_ridges = morphology.thin(neighboring_ridges)
    return all_ridges, extended_ridges, neighboring_ridges