Пример #1
0
def test_return_count():
    labels = np.ones((10, 10, 10), dtype=bool)
    labels[3:6, 3:6, 3:6] = False

    filled = fill_voids.fill(labels)
    assert np.all(filled == 1)

    filled, ct = fill_voids.fill(labels, return_fill_count=True)
    assert np.any(labels == False)
    assert ct == 27
Пример #2
0
def postrocessing(label_image, min_area):
    '''some post-processing mapping small label patches to the neighbout whith which they share the
        largest border. All connected components smaller than min_area will be removed
    '''
    # cleaning overall connected components and fill holes
    regionmask = skimage.measure.label(label_image > 0)
    regions = skimage.measure.regionprops(regionmask)
    resizes = np.asarray([x.area for x in regions])
    m = len(resizes)
    ix = np.zeros((m, ), dtype=np.uint8)
    ix[resizes > min_area] = 1
    ix = np.concatenate([[
        0,
    ], ix])
    cleaned = ix[regionmask]

    outmask = np.zeros(cleaned.shape, dtype=np.uint8)
    for i in np.unique(label_image)[1:]:
        outmask[fill_voids.fill((label_image == i) & (cleaned > 0))] = i

    # merge small components to neighbours
    regionmask = skimage.measure.label(outmask)
    origlabels = np.unique(outmask)
    origlabels_maxsub = np.zeros(
        (max(origlabels) + 1, ),
        dtype=np.uint32)  # will hold the largest component for a label
    regions = skimage.measure.regionprops(regionmask, outmask)
    regions.sort(key=lambda x: x.area)
    regionlabels = [x.label for x in regions]

    # will hold mapping from regionlabels to original labels
    region_to_lobemap = np.zeros((len(regionlabels) + 1, ), dtype=np.uint8)
    for r in regions:
        if r.area > origlabels_maxsub[r.max_intensity]:
            origlabels_maxsub[r.max_intensity] = r.area
            region_to_lobemap[r.label] = r.max_intensity

    for r in regions:
        if r.area < origlabels_maxsub[r.max_intensity]:
            bb = bbox_3D(regionmask == r.label)
            sub = regionmask[bb[0]:bb[1], bb[2]:bb[3], bb[4]:bb[5]]
            dil = ndimage.binary_dilation(sub == r.label)
            neighbours, counts = np.unique(sub[dil], return_counts=True)
            mapto = r.label
            maxmap = 0
            myarea = 0
            for ix, n in enumerate(neighbours):
                if n != 0 and n != r.label and counts[ix] > maxmap:
                    maxmap = counts[ix]
                    mapto = n
                    myarea = r.area
            regionmask[regionmask == r.label] = mapto
            if regions[regionlabels.index(mapto)].area == origlabels_maxsub[
                    regions[regionlabels.index(mapto)].max_intensity]:
                origlabels_maxsub[regions[regionlabels.index(
                    mapto)].max_intensity] += myarea
            regions[regionlabels.index(
                mapto)].__dict__['_cache']['area'] += myarea

    return region_to_lobemap[regionmask]
Пример #3
0
def execute(seg: Chunk):
    properties = seg.properties
    array = fill_voids.fill(seg.array)
    seg2 = Chunk(array)
    seg2.set_properties(properties)
    return [
        seg2,
    ]
Пример #4
0
def engage_avocado_protection_single_pass(cc_labels,
                                          all_dbf,
                                          candidates=None,
                                          progress=False):
    """
  For each candidate, check if there's a fruit around the
  avocado pit roughly from the center (the max EDT).
  """

    if candidates is None:
        candidates = fastremap.unique(cc_labels)

    candidates = [label for label in candidates if label != 0]

    unchanged = set()
    changed = set()

    if len(candidates) == 0:
        return cc_labels, unchanged, changed

    def paint_walls(binimg):
        """
    Ensure that inclusions that touch the wall are handled
    by performing a 2D fill on each wall.
    """
        binimg[:, :, 0] = fill_voids.fill(binimg[:, :, 0])
        binimg[:, :, -1] = fill_voids.fill(binimg[:, :, -1])
        binimg[:, 0, :] = fill_voids.fill(binimg[:, 0, :])
        binimg[:, -1, :] = fill_voids.fill(binimg[:, -1, :])
        binimg[0, :, :] = fill_voids.fill(binimg[0, :, :])
        binimg[-1, :, :] = fill_voids.fill(binimg[-1, :, :])
        return binimg

    remap = {}
    for label in tqdm(candidates,
                      disable=(not progress),
                      desc="Fixing Avocados"):
        binimg = paint_walls(cc_labels == label)  # image of the pit
        coord = argmax(binimg * all_dbf)

        (pit, fruit) = kimimaro.skeletontricks.find_avocado_fruit(
            cc_labels, coord[0], coord[1], coord[2])
        if pit == fruit and pit not in changed:
            unchanged.add(pit)
        else:
            unchanged.discard(pit)
            unchanged.discard(fruit)
            changed.add(pit)
            changed.add(fruit)
            binimg |= (cc_labels == fruit)

        binimg, N = fill_voids.fill(binimg,
                                    in_place=True,
                                    return_fill_count=True)
        cc_labels *= ~binimg
        cc_labels += label * binimg

    return cc_labels, unchanged, changed
Пример #5
0
def test_scipy_comparison2d():
    segids = np.copy(SEGIDS)
    np.random.shuffle(segids)

    for segid in tqdm(segids[:10]):
        print(segid)
        for z in tqdm(range(img.shape[2])):
            binimg = img[:, :, z] == segid

            orig_binimg = np.copy(binimg, order='F')
            fv = fill_voids.fill(binimg, in_place=False)
            fvip = fill_voids.fill(binimg, in_place=True)

            assert np.all(fv == fvip)

            spy = binary_fill_holes(binimg)

            assert np.all(fv == spy)
Пример #6
0
def test_scipy_comparison3d():
    segids = np.copy(SEGIDS)
    np.random.shuffle(segids)

    for segid in tqdm(segids[:10]):
        print(segid)
        binimg = img == segid
        slices = scipy.ndimage.find_objects(binimg)[0]
        binimg = binimg[slices]

        orig_binimg = np.copy(binimg, order='F')
        fv = fill_voids.fill(binimg, in_place=False)
        fvip = fill_voids.fill(binimg, in_place=True)

        assert np.all(fv == fvip)

        spy = binary_fill_holes(binimg)

        assert np.all(fv == spy)
Пример #7
0
def test_dimensions(dimension):
    size = [5] * dimension
    for i in range(3, dimension):
        size[i] = 1

    labels = np.ones(size, dtype=np.uint8)
    labels = fill_voids.fill(labels)
    assert labels.ndim == dimension

    if dimension <= 3:
        return

    size[dimension - 1] = 2
    labels = np.ones(size, dtype=np.uint8)
    try:
        labels = fill_voids.fill(labels)
        assert False
    except fill_voids.DimensionError:
        pass
Пример #8
0
def test_2d_3d_differ():
    labels = np.zeros((10, 10), dtype=np.bool)
    labels[1:9, 1:9] = True
    labels[4:8, 4:8] = False

    expected_result2d = np.zeros((10, 10), dtype=np.bool)
    expected_result2d[1:9, 1:9] = True

    expected_result3d = np.copy(labels).reshape(10, 10, 1)

    filled_labels, N = fill_voids.fill(labels,
                                       in_place=False,
                                       return_fill_count=True)
    assert N == 16
    assert np.all(filled_labels == expected_result2d)

    labels = labels[..., np.newaxis]

    filled_labels, N = fill_voids.fill(labels,
                                       in_place=False,
                                       return_fill_count=True)
    assert N == 0
    assert np.all(filled_labels == expected_result3d)
Пример #9
0
def test_zero_array():
    labels = np.zeros((0, ), dtype=np.uint8)
    # just don't throw an exception
    fill_voids.fill(labels, in_place=False)
    fill_voids.fill(labels, in_place=True)

    labels = np.zeros((128, 128, 128), dtype=np.uint8)
    fill_voids.fill(labels, in_place=True)
    assert not np.any(labels)
Пример #10
0
def fill_all_holes(cc_labels, progress=False, return_fill_count=False):
    """
  Fills the holes in each connected component and removes components that
  get filled in. The idea is that holes (entirely contained labels or background) 
  are artifacts in cell segmentations. A common example is a nucleus segmented 
  separately from the rest of the cell or errors in a manual segmentation leaving
  a void in a dendrite.

  cc_labels: an image containing connected components with labels smaller than
    the number of voxels in the image.
  progress: Display a progress bar or not.
  return_fill_count: if specified, return a tuple (filled_image, N) where N is
    the number of voxels that were filled in.

  Returns: filled_in_labels
  """
    labels = fastremap.unique(cc_labels)
    labels_set = set(labels)
    labels_set.discard(0)

    all_slices = find_objects(cc_labels)
    pixels_filled = 0

    for label in tqdm(labels, disable=(not progress), desc="Filling Holes"):
        if label not in labels_set:
            continue

        slices = all_slices[label - 1]
        if slices is None:
            continue

        binary_image = (cc_labels[slices] == label)
        binary_image, N = fill_voids.fill(binary_image,
                                          in_place=True,
                                          return_fill_count=True)
        pixels_filled += N
        if N == 0:
            continue

        sub_labels = set(fastremap.unique(cc_labels[slices] * binary_image))
        sub_labels.remove(label)
        labels_set -= sub_labels
        cc_labels[
            slices] = cc_labels[slices] * ~binary_image + label * binary_image

    if return_fill_count:
        return cc_labels, pixels_filled
    return cc_labels
Пример #11
0
 def paint_walls(binimg):
     """
 Ensure that inclusions that touch the wall are handled
 by performing a 2D fill on each wall.
 """
     binimg[:, :, 0] = fill_voids.fill(binimg[:, :, 0])
     binimg[:, :, -1] = fill_voids.fill(binimg[:, :, -1])
     binimg[:, 0, :] = fill_voids.fill(binimg[:, 0, :])
     binimg[:, -1, :] = fill_voids.fill(binimg[:, -1, :])
     binimg[0, :, :] = fill_voids.fill(binimg[0, :, :])
     binimg[-1, :, :] = fill_voids.fill(binimg[-1, :, :])
     return binimg
Пример #12
0
def trace(
    labels,
    DBF,
    scale=10,
    const=10,
    anisotropy=(1, 1, 1),
    soma_detection_threshold=1100,
    soma_acceptance_threshold=4000,
    pdrf_scale=5000,
    pdrf_exponent=16,
    soma_invalidation_scale=0.5,
    soma_invalidation_const=0,
    fix_branching=True,
    manual_targets_before=[],
    manual_targets_after=[],
    root=None,
    max_paths=None,
    voxel_graph=None,
):
    """
  Given the euclidean distance transform of a label ("Distance to Boundary Function"), 
  convert it into a skeleton using an algorithm based on TEASAR. 

  DBF: Result of the euclidean distance transform. Must represent a single label,
       assumed to be expressed in chosen physical units (i.e. nm)
  scale: during the "rolling ball" invalidation phase, multiply the DBF value by this.
  const: during the "rolling ball" invalidation phase, this is the minimum radius in chosen physical units (i.e. nm).
  anisotropy: (x,y,z) conversion factor for voxels to chosen physical units (i.e. nm)
  soma_detection_threshold: if object has a DBF value larger than this, 
    root will be placed at largest DBF value and special one time invalidation
    will be run over that root location (see soma_invalidation scale)
    expressed in chosen physical units (i.e. nm) 
  pdrf_scale: scale factor in front of dbf, used to weight dbf over euclidean distance (higher to pay more attention to dbf) (default 5000)
  pdrf_exponent: exponent in dbf formula on distance from edge, faster if factor of 2 (default 16)
  soma_invalidation_scale: the 'scale' factor used in the one time soma root invalidation (default .5)
  soma_invalidation_const: the 'const' factor used in the one time soma root invalidation (default 0)
                           (units in chosen physical units (i.e. nm))
  fix_branching: When enabled, zero out the graph edge weights traversed by 
    of previously found paths. This causes branch points to occur closer to 
    the actual path divergence. However, there is a large performance penalty
    associated with this as dijkstra's algorithm is computed once per a path
    rather than once per a skeleton.
  manual_targets_before: list of (x,y,z) that correspond to locations that must 
    have paths drawn to. Used for specifying root and border targets for
    merging adjacent chunks out-of-core. Targets are applied before ordinary
    target selection.
  manual_targets_after: Same as manual_targets_before but the additional 
    targets are applied after the usual algorithm runs. The current 
    invalidation status of the shape makes no difference.
  max_paths: If a label requires drawing this number of paths or more,
    abort and move onto the next label.
  root: If you want to force the root to be a particular voxel, you can
    specify it here.
  voxel_graph: a connection graph that defines permissible 
    directions of motion between voxels. This is useful for
    dealing with self-touches. The graph is defined by the
    conventions used in cc3d.voxel_connectivity_graph 
    (https://github.com/seung-lab/connected-components-3d/blob/3.2.0/cc3d_graphs.hpp#L73-L92)

  Based on the algorithm by:

  M. Sato, I. Bitter, M. Bender, A. Kaufman, and M. Nakajima. 
  "TEASAR: tree-structure extraction algorithm for accurate and robust skeletons"  
    Proc. the Eighth Pacific Conference on Computer Graphics and Applications. Oct. 2000.
    doi:10.1109/PCCGA.2000.883951 (https://ieeexplore.ieee.org/document/883951/)

  Returns: Skeleton object
  """
    dbf_max = np.max(DBF)
    labels = np.asfortranarray(labels)
    DBF = np.asfortranarray(DBF)

    soma_mode = False
    # > 5000 nm, gonna be a soma or blood vessel
    # For somata: specially handle the root by
    # placing it at the approximate center of the soma
    if dbf_max > soma_detection_threshold:
        labels, num_voxels_filled = fill_voids.fill(labels,
                                                    in_place=True,
                                                    return_fill_count=True)
        if num_voxels_filled > 0:
            del DBF
            DBF = edt.edt(labels,
                          anisotropy=anisotropy,
                          order='F',
                          black_border=np.all(labels))
        dbf_max = np.max(DBF)
        soma_mode = dbf_max > soma_acceptance_threshold

    soma_radius = 0.0

    if soma_mode:
        if root is not None:
            manual_targets_before.insert(0, root)
        root = find_soma_root(DBF, dbf_max)
        soma_radius = dbf_max * soma_invalidation_scale + soma_invalidation_const
    elif root is None:
        root = find_root(labels, anisotropy)

    if root is None:
        return PrecomputedSkeleton()

    free_space_radius = 0 if not soma_mode else DBF[root]
    # DBF: Distance to Boundary Field
    # DAF: Distance from any voxel Field (distance from root field)
    # PDRF: Penalized Distance from Root Field
    DBF = kimimaro.skeletontricks.zero2inf(DBF)  # DBF[ DBF == 0 ] = np.inf
    DAF, target = dijkstra3d.euclidean_distance_field(
        labels,
        root,
        anisotropy=anisotropy,
        free_space_radius=free_space_radius,
        voxel_graph=voxel_graph,
        return_max_location=True,
    )
    DAF = kimimaro.skeletontricks.inf2zero(DAF)  # DAF[ DAF == np.inf ] = 0
    PDRF = compute_pdrf(dbf_max, pdrf_scale, pdrf_exponent, DBF, DAF)

    # Use dijkstra propogation w/o a target to generate a field of
    # pointers from each voxel to its parent. Then we can rapidly
    # compute multiple paths by simply hopping pointers using path_from_parents
    if not fix_branching:
        parents = dijkstra3d.parental_field(PDRF, root, voxel_graph)
        del PDRF
    else:
        parents = PDRF

    if soma_mode:
        invalidated, labels = kimimaro.skeletontricks.roll_invalidation_ball(
            labels,
            DBF,
            np.array([root], dtype=np.uint32),
            scale=soma_invalidation_scale,
            const=soma_invalidation_const,
            anisotropy=anisotropy)
    # This target is only valid if no
    # invalidations have occured yet.
    elif len(manual_targets_before) == 0:
        manual_targets_before.append(target)

    # delete reference to DAF and place it in
    # a list where we can delete it later and
    # free that memory.
    DAF = [DAF]

    paths = compute_paths(root, labels, DBF, DAF, parents, scale, const,
                          anisotropy, soma_mode, soma_radius, fix_branching,
                          manual_targets_before, manual_targets_after,
                          max_paths, voxel_graph)

    skel = PrecomputedSkeleton.simple_merge([
        PrecomputedSkeleton.from_path(path) for path in paths if len(path) > 0
    ]).consolidate()

    verts = skel.vertices.flatten().astype(np.uint32)
    skel.radii = DBF[verts[::3], verts[1::3], verts[2::3]]

    return skel
Пример #13
0
def mask_to_cells_edge(prediction_mask,
                       im,
                       config,
                       r_min,
                       frame_data,
                       edge_dist=15,
                       return_mask=False):
    r_min_pix = r_min / config["pixel_size_m"] / 1e6
    edge_dist_pix = edge_dist / config["pixel_size_m"] / 1e6
    cells = []
    # TDOD: consider first applying binary closing operations to avoid impact of very small gaps in the cell border
    filled = fill_voids.fill(prediction_mask)
    # iterate over all detected regions
    for region in regionprops(
            label(filled)):  # region props are based on the original image
        # checking if the anything was filled up by extracting the region form the original image
        # if no significant region was filled, we skip this object
        yc, xc = np.split(region.coords, 2, axis=1)
        if np.sum(~prediction_mask[yc.flatten(), xc.flatten()]) < 10:
            if return_mask:
                prediction_mask[yc.flatten(), xc.flatten()] = False
            continue
        elif return_mask:
            prediction_mask[yc.flatten(), xc.flatten()] = True

        a = region.major_axis_length / 2
        b = region.minor_axis_length / 2
        r = np.sqrt(a * b)

        if region.orientation > 0:
            ellipse_angle = np.pi / 2 - region.orientation
        else:
            ellipse_angle = -np.pi / 2 - region.orientation

        Amin_pixels = np.pi * (
            r_min_pix)**2  # minimum region area based on minimum radius
        # filtering cells close to left and right image edge
        # usually cells do not come close to upper and lower image edge
        x_pos = region.centroid[1]
        dist_to_edge = np.min([x_pos, prediction_mask.shape[1] - x_pos])

        if region.area >= Amin_pixels and dist_to_edge > edge_dist_pix:  # analyze only regions larger than 100 pixels,
            # and only of the canny filtered band-passed image returend an object

            # the circumference of the ellipse
            circum = np.pi * ((3 * (a + b)) - np.sqrt(10 * a * b + 3 *
                                                      (a**2 + b**2)))

            if 0:
                # %% compute radial intensity profile around each ellipse
                theta = np.arange(0, 2 * np.pi, np.pi / 8)

                i_r = np.zeros(int(3 * r))
                for d in range(0, int(3 * r)):
                    # get points on the circumference of the ellipse
                    x = d / r * a * np.cos(theta)
                    y = d / r * b * np.sin(theta)
                    # rotate the points by the angle fo the ellipse
                    t = ellipse_angle
                    xrot = (x * np.cos(t) - y * np.sin(t) +
                            region.centroid[1]).astype(int)
                    yrot = (x * np.sin(t) + y * np.cos(t) +
                            region.centroid[0]).astype(int)
                    # crop for points inside the iamge
                    index = (xrot < 0) | (xrot >= im.shape[1]) | (yrot < 0) | (
                        yrot >= im.shape[0])
                    x = xrot[~index]
                    y = yrot[~index]
                    # average over all these points
                    i_r[d] = np.mean(im[y, x])

                # define a sharpness value
                sharp = (i_r[int(r + 2)] - i_r[int(r - 2)]) / 5 / np.std(i_r)
            sharp = 0

            # %% store the cells
            yy = region.centroid[0] - config["channel_width_px"] / 2
            yy = yy * config["pixel_size_m"] * 1e6

            data = {}

            data.update(frame_data)
            data.update({
                "x":
                region.centroid[1],  # x_pos
                "y":
                region.centroid[0],  # y_pos
                "rp":
                yy,  # RadialPos
                "long_axis":
                float(format(region.major_axis_length)) *
                config["pixel_size_m"] * 1e6,  # LongAxis
                "short_axis":
                float(format(region.minor_axis_length)) *
                config["pixel_size_m"] * 1e6,  # ShortAxis
                "angle":
                np.rad2deg(ellipse_angle),  # angle
                "irregularity":
                region.perimeter / circum,  # irregularity
                "solidity":
                region.solidity,  # solidity
                "sharpness":
                sharp,  # sharpness
            })
            cells.append(data)
    if return_mask:
        return cells, prediction_mask
    else:
        return cells
Пример #14
0
def test_dtypes(dtype):
    binimg = img == SEGIDS[0]

    comparison = fill_voids.fill(binimg, in_place=False)
    res = fill_voids.fill(binimg.astype(dtype), in_place=False)
    assert np.all(comparison == res)