def test_trace_boundary(self):

        # test moore neighbor algorithm

        m_neighbor = np.array(
            [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 1, 1, 0],
             [0, 0, 0, 0, 0, 0, 0, 1, 1, 0], [0, 0, 0, 1, 1, 1, 1, 0, 0, 0],
             [0, 0, 1, 1, 1, 1, 1, 0, 0, 0], [0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
             [0, 0, 1, 1, 1, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 1, 1, 0, 0, 0],
             [0, 0, 0, 0, 0, 0, 0, 1, 1, 0], [0, 0, 0, 0, 0, 0, 0, 1, 1, 0],
             [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
            dtype=np.bool)

        # refenece neighbors for isbf
        rx_isbf = [
            1, 1, 2, 2, 3, 4, 5, 6, 7, 8, 8, 9, 9, 8, 7, 7, 7, 7, 6, 6, 5, 5,
            5, 4, 4, 3, 3, 3, 3, 2, 1
        ]
        ry_isbf = [
            7, 8, 8, 7, 6, 6, 6, 6, 6, 7, 8, 8, 7, 7, 6, 5, 4, 3, 3, 2, 2, 1,
            2, 2, 3, 3, 4, 5, 6, 7, 7
        ]

        # refenece neighbors for moore
        rx_moore = [
            1, 1, 2, 2, 3, 4, 5, 6, 7, 8, 8, 9, 9, 8, 7, 7, 7, 7, 6, 5, 4, 3,
            3, 3, 3, 2, 1
        ]
        ry_moore = [
            7, 8, 8, 7, 6, 6, 6, 6, 6, 7, 8, 8, 7, 7, 6, 5, 4, 3, 2, 1, 2, 3,
            4, 5, 6, 7, 7
        ]

        output_isbf = trace_boundaries(m_neighbor)

        np.testing.assert_allclose(rx_isbf, output_isbf[0][1])
        np.testing.assert_allclose(ry_isbf, output_isbf[0][0])

        output_moore = trace_boundaries(m_neighbor, 8)

        np.testing.assert_allclose(rx_moore, output_moore[0][1])
        np.testing.assert_allclose(ry_moore, output_moore[0][0])
    def test_trace_boundary(self):

        # test moore neighbor algorithm

        m_neighbor = np.array(
            [
                [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                [0, 0, 0, 0, 0, 0, 0, 1, 1, 0],
                [0, 0, 0, 0, 0, 0, 0, 1, 1, 0],
                [0, 0, 0, 1, 1, 1, 1, 0, 0, 0],
                [0, 0, 1, 1, 1, 1, 1, 0, 0, 0],
                [0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
                [0, 0, 1, 1, 1, 1, 1, 0, 0, 0],
                [0, 0, 0, 1, 1, 1, 1, 0, 0, 0],
                [0, 0, 0, 0, 0, 0, 0, 1, 1, 0],
                [0, 0, 0, 0, 0, 0, 0, 1, 1, 0],
                [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
            ],
            dtype=np.bool,
        )

        m_neighbor = np.ascontiguousarray(m_neighbor, dtype=np.int)

        # refenece neighbors for isbf
        rx_isbf = [1, 1, 2, 2, 3, 4, 5, 6, 7, 8, 8, 9, 9, 8, 7, 7, 7, 7, 6, 6, 5, 5, 5, 4, 4, 3, 3, 3, 3, 2, 1]
        ry_isbf = [7, 8, 8, 7, 6, 6, 6, 6, 6, 7, 8, 8, 7, 7, 6, 5, 4, 3, 3, 2, 2, 1, 2, 2, 3, 3, 4, 5, 6, 7, 7]

        # refenece neighbors for moore
        rx_moore = [1, 1, 2, 2, 3, 4, 5, 6, 7, 8, 8, 9, 9, 8, 7, 7, 7, 7, 6, 5, 4, 3, 3, 3, 3, 2, 1]
        ry_moore = [7, 8, 8, 7, 6, 6, 6, 6, 6, 7, 8, 8, 7, 7, 6, 5, 4, 3, 2, 1, 2, 3, 4, 5, 6, 7, 7]

        output_isbf = trace_boundaries(m_neighbor)

        np.testing.assert_allclose(rx_isbf, output_isbf[0][1])
        np.testing.assert_allclose(ry_isbf, output_isbf[0][0])

        output_moore = trace_boundaries(m_neighbor, 8)

        np.testing.assert_allclose(rx_moore, output_moore[0][1])
        np.testing.assert_allclose(ry_moore, output_moore[0][0])
예제 #3
0
def split_concavities(Label, MinDepth=4, MinConcavity=np.inf):  # noqa: C901
    """Performs splitting of objects in a label image using geometric scoring
    of concavities. Attempts to perform splits at narrow regions that are
    perpendicular to the object's convex hull boundaries.

    Parameters:
    -----------
    Label : array_like
        A uint32 label image.
    MinDepth : float
        Minimum depth of concavities to consider during geometric splitting.
        Default value = 2.
    MinConcavity : float
        Minimum concavity score to consider when performing for geometric
        splitting. Default value = np.inf.

    Notes:
    ------
    Can produce a large number of thin "halo" objects surrouding the objects
    with higher scores. These can be removed by filtering object width in the
    resulting label image.

    Returns:
    --------
    Label : array_like
        A uint32 label image.

    See Also:
    ---------
    label_contours, min_model

    References:
    -----------
    .. [1] S. Weinert et al "Detection and Segmentation of Cell Nuclei in
    Virtual Microscopy Images: A Minimum-Model Approach" in Nature Scientific
    Reports,vol.2,no.503, doi:10.1038/srep00503, 2012.
    """

    # use shape profiles to split objects with concavities

    # copy input label image
    Convex = Label.copy()

    # condense label image
    if np.unique(Convex).size-1 != Convex.max():
        Convex = label.condense(Convex)

    # get locations of objects in initial image
    Locations = ms.find_objects(Convex)

    # initialize number of labeled objects and record initial count
    Total = Label.max()

    # initialize loop counter
    i = 1

    while i <= Total:

        # get object window from label image
        if i < len(Locations):
            W = Convex[Locations[i-1]]
        else:
            Locations = ms.find_objects(Convex)
            W = Convex[Locations[i-1]]

        # embed masked object in padded boolean array
        Mask = np.zeros((W.shape[0]+2, W.shape[1]+2), dtype=np.bool)
        Mask[1:-1, 1:-1] = W == i

        # generate convex hull of object
        Hull = mo.convex_hull_image(Mask)

        # generate boundary coordinates, trim duplicate point
        X, Y = label.trace_boundaries(Mask, Connectivity=8)
        X = np.array(X[:-1], dtype=np.uint32)
        Y = np.array(Y[:-1], dtype=np.uint32)

        # calculate distance transform of object boundary pixels to convex hull
        Distance = mp.distance_transform_edt(Hull)
        D = Distance[Y, X] - 1

        # generate linear index of positions
        Linear = np.arange(X.size)

        # rotate boundary counter-clockwise until start position is on hull
        while(D[0] != 0):
            X = np.roll(X, -1)
            Y = np.roll(Y, -1)
            D = np.roll(D, -1)
            Linear = np.roll(Linear, -1)

        # find runs of concave pixels with length > 1
        Concave = (D > 0).astype(np.int)
        Start = np.where((Concave[1:] - Concave[0:-1]) == 1)[0]
        Stop = np.where((Concave[1:] - Concave[0:-1]) == -1)[0] + 1
        if(Stop.size == Start.size - 1):
            Stop = np.append(Stop, 0)

        # extract depth profiles, indices, distances for each run
        iX = []
        iY = []
        Depths = []
        Length = np.zeros((Start.size))
        MaxDepth = np.zeros((Start.size))
        for j in np.arange(Start.size):
            if(Start[j] < Stop[j]):
                iX.append(X[Start[j]:Stop[j]+1])
                iY.append(Y[Start[j]:Stop[j]+1])
                Depths.append(D[Start[j]:Stop[j]+1])
            else:  # run terminates at beginning of sequence
                iX.append(np.append(X[Start[j]:], X[0]))
                iY.append(np.append(Y[Start[j]:], Y[0]))
                Depths.append(np.append(D[Start[j]:], D[0]))
            Length[j] = iX[j].size
            MaxDepth[j] = np.max(Depths[j])

        # filter based on concave contour length and max depth
        Keep = np.where((Length > 1) & (MaxDepth >= MinDepth))[0]
        Start = Start[Keep]
        Stop = Stop[Keep]
        iX = [iX[Ind].astype(dtype=np.float) for Ind in Keep]
        iY = [iY[Ind].astype(dtype=np.float) for Ind in Keep]
        Depths = [Depths[Ind] for Ind in Keep]

        # attempt cutting if more than 1 sequence is found
        if Start.size > 1:

            # initialize containers to hold cut scores, optimal cut locations
            Scores = np.inf * np.ones((Start.size, Start.size))
            Xcut1 = np.zeros((Start.size, Start.size), dtype=np.uint32)
            Ycut1 = np.zeros((Start.size, Start.size), dtype=np.uint32)
            Xcut2 = np.zeros((Start.size, Start.size), dtype=np.uint32)
            Ycut2 = np.zeros((Start.size, Start.size), dtype=np.uint32)

            # compare candidates pairwise between all runs and score
            for j in np.arange(Start.size):

                # get list of 'j' candidates that pass depth threshold
                jCandidates = np.where(Depths[j] >= MinDepth)[0]

                for k in np.arange(j+1, Start.size):

                    # get list of 'k' candidates that pass depth threshold
                    kCandidates = np.where(Depths[k] >= MinDepth)[0]

                    # initialize minimum score and cut locations
                    minScore = np.inf
                    minj = -1
                    mink = -1

                    # loop over each coordinate pair for concavities j,k
                    for a in np.arange(jCandidates.size):
                        for b in np.arange(kCandidates.size):

                            # calculate length score
                            Ls = length_score(iX[j][jCandidates[a]],
                                              iY[j][jCandidates[a]],
                                              iX[k][kCandidates[b]],
                                              iY[k][kCandidates[b]],
                                              Depths[j][jCandidates[a]],
                                              Depths[k][kCandidates[b]])

                            # calculate angle score
                            As = angle_score(iX[j][0], iY[j][0],
                                             iX[j][-1], iY[j][-1],
                                             iX[k][0], iY[k][0],
                                             iX[k][-1], iY[k][-1],
                                             iX[j][jCandidates[a]],
                                             iY[j][jCandidates[a]],
                                             iX[k][kCandidates[b]],
                                             iY[k][kCandidates[b]])

                            # combine scores
                            Score = (Ls + As) / 2

                            # replace if improvement
                            if Score < minScore:
                                minScore = Score
                                Scores[j, k] = minScore
                                minj = jCandidates[a]
                                mink = kCandidates[b]

                    # record best cut location
                    Xcut1[j, k] = iX[j][minj]
                    Ycut1[j, k] = iY[j][minj]
                    Xcut2[j, k] = iX[k][mink]
                    Ycut2[j, k] = iY[k][mink]

            # pick the best scoring candidates and cut if needed
            ArgMin = np.unravel_index(Scores.argmin(), Scores.shape)
            if Scores[ArgMin[0], ArgMin[1]] <= MinConcavity:

                # perform cut
                SplitMask = cut(Mask,
                                Xcut1[ArgMin[0], ArgMin[1]].astype(np.float),
                                Ycut1[ArgMin[0], ArgMin[1]].astype(np.float),
                                Xcut2[ArgMin[0], ArgMin[1]].astype(np.float),
                                Ycut2[ArgMin[0], ArgMin[1]].astype(np.float))

                # re-label cut image
                SplitLabel = ms.label(SplitMask)[0]

                # increment object count, and label new object at end
                SplitLabel[SplitLabel > 1] = Total + 1
                Total += 1

                # label object '1' with current object value
                SplitLabel[SplitLabel == 1] = i

                # trim padding from corrected label image
                Mask = Mask[1:-1, 1:-1]
                SplitLabel = SplitLabel[1:-1, 1:-1]

                # update label image
                W[Mask] = SplitLabel[Mask]

            else:  # no cut made, move to next object
                i = i + 1
        else:  # no cuts to attempt, move to next object
            i = i + 1

    return Convex
예제 #4
0
def trace_contours(I, X, Y, Min, Max, MaxLength=255):
    """Performs contour tracing of seed pixels in an intensity image using
    gradient information.

    Parameters:
    -----------
    I : array_like
        An intensity image used for analyzing local minima/maxima and
        gradients. Dimensions M x N.
    X : array_like
        A 1D array of horizontal coordinates of contour seed pixels for
        tracing.
    Y : array_like
        A 1D array of the vertical coordinates of seed pixels for tracing.
    Min : array_like
        A 1D array of the corresponding minimum values for contour tracing of
        seed point X, Y.
    Max : array_like
        A 1D array of the corresponding maximum values for contour tracing of
        seed point X, Y.
    MaxLength : int
        Maximum allowable contour length. Default value = 255.

    Notes:
    ------
    Can be computationally expensive for large numbers of contours. Use
    smoothing and delta thresholding when seeding contours to reduce burden.

    Returns:
    --------
    cXs : list
        A list of 1D numpy arrays defining the horizontal coordinates of object
        boundaries.
    cYs : list
        A list of 1D numpy arrays defining the vertical coordinates of object
        boundaries.

    See Also:
    ---------
    SeedContours, ScoreContours, MinimumModel

    References:
    -----------
    .. [1] S. Weinert et al "Detection and Segmentation of Cell Nuclei in
    Virtual Microscopy Images: A Minimum-Model Approach" in Nature Scientific
    Reports,vol.2,no.503, doi:10.1038/srep00503, 2012.
    """

    # initialize list of lists containing contours
    cXs = []
    cYs = []

    # process each seed pixel sequentially
    for i in np.arange(X.size):

        # capture window surrounding (X[i], Y[i])
        W = I[max(0, Y[i]-np.ceil(MaxLength/2.0)):
              min(I.shape[0]+1, Y[i]+np.ceil(MaxLength/2.0)+1),
              max(0, X[i]-np.ceil(MaxLength/2.0)):
              min(I.shape[1]+1, X[i]+np.ceil(MaxLength/2.0))+1]

        # binary threshold corresponding to seed pixel 'i'
        W = (W <= Max[i]) & (W >= Min[i])

        # embed with center pixel in middle of padded window
        Embed = np.zeros((W.shape[0]+2, W.shape[1]+2), dtype=np.bool)
        Embed[1:-1, 1:-1] = W

        # calculate location of (X[i], Y[i]) in 'Embed'
        pX = X[i] - max(0, X[i]-np.ceil(MaxLength/2.0)) + 1
        pY = Y[i] - max(0, Y[i]-np.ceil(MaxLength/2.0)) + 1

        # trace boundary, check stopping condition, append to list of contours
        cX, cY = label.trace_boundaries(Embed, Connectivity=4,
                                        XStart=pX, YStart=pY,
                                        MaxLength=MaxLength)
        if(cX[0] == cX[-1] and cY[0] == cY[-1] and len(cX) <= MaxLength):

            # add window offset to contour coordinates
            cX = [x + max(0, X[i]-np.ceil(MaxLength/2.0)) - 1 for x in cX]
            cY = [y + max(0, Y[i]-np.ceil(MaxLength/2.0)) - 1 for y in cY]

            # append to list of candidate contours
            cXs.append(np.array(cX, dtype=np.uint32))
            cYs.append(np.array(cY, dtype=np.uint32))

    return cXs, cYs
예제 #5
0
def split_concavities(Label, MinDepth=4, MinConcavity=np.inf):  # noqa: C901
    """Performs splitting of objects in a label image using geometric scoring
    of concavities. Attempts to perform splits at narrow regions that are
    perpendicular to the object's convex hull boundaries.

    Parameters
    ----------
    Label : array_like
        A uint32 label image.
    MinDepth : float
        Minimum depth of concavities to consider during geometric splitting.
        Default value = 2.
    MinConcavity : float
        Minimum concavity score to consider when performing for geometric
        splitting. Default value = np.inf.

    Notes
    -----
    Can produce a large number of thin "halo" objects surrouding the objects
    with higher scores. These can be removed by filtering object width in the
    resulting label image.

    Returns
    -------
    Label : array_like
        A uint32 label image.

    See Also
    --------
    label_contours, min_model

    References
    ----------
    .. [1] S. Weinert et al "Detection and Segmentation of Cell Nuclei in
    Virtual Microscopy Images: A Minimum-Model Approach" in Nature Scientific
    Reports,vol.2,no.503, doi:10.1038/srep00503, 2012.
    """

    # use shape profiles to split objects with concavities

    # copy input label image
    Convex = Label.copy()

    # condense label image
    if np.unique(Convex).size - 1 != Convex.max():
        Convex = label.condense(Convex)

    # get locations of objects in initial image
    Locations = ms.find_objects(Convex)

    # initialize number of labeled objects and record initial count
    Total = Label.max()

    # initialize loop counter
    i = 1

    while i <= Total:

        # get object window from label image
        if i < len(Locations):
            W = Convex[Locations[i - 1]]
        else:
            Locations = ms.find_objects(Convex)
            W = Convex[Locations[i - 1]]

        # embed masked object in padded boolean array
        Mask = np.zeros((W.shape[0] + 2, W.shape[1] + 2), dtype=np.bool)
        Mask[1:-1, 1:-1] = W == i

        # generate convex hull of object
        Hull = mo.convex_hull_image(Mask)

        # generate boundary coordinates, trim duplicate point
        X, Y = label.trace_boundaries(Mask, conn=8)
        X = np.array(X[:-1], dtype=np.uint32)
        Y = np.array(Y[:-1], dtype=np.uint32)

        # calculate distance transform of object boundary pixels to convex hull
        Distance = mp.distance_transform_edt(Hull)
        D = Distance[Y, X] - 1

        # generate linear index of positions
        Linear = np.arange(X.size)

        # rotate boundary counter-clockwise until start position is on hull
        while (D[0] != 0):
            X = np.roll(X, -1)
            Y = np.roll(Y, -1)
            D = np.roll(D, -1)
            Linear = np.roll(Linear, -1)

        # find runs of concave pixels with length > 1
        Concave = (D > 0).astype(np.int)
        Start = np.where((Concave[1:] - Concave[0:-1]) == 1)[0]
        Stop = np.where((Concave[1:] - Concave[0:-1]) == -1)[0] + 1
        if (Stop.size == Start.size - 1):
            Stop = np.append(Stop, 0)

        # extract depth profiles, indices, distances for each run
        iX = []
        iY = []
        Depths = []
        Length = np.zeros((Start.size))
        MaxDepth = np.zeros((Start.size))
        for j in np.arange(Start.size):
            if (Start[j] < Stop[j]):
                iX.append(X[Start[j]:Stop[j] + 1])
                iY.append(Y[Start[j]:Stop[j] + 1])
                Depths.append(D[Start[j]:Stop[j] + 1])
            else:  # run terminates at beginning of sequence
                iX.append(np.append(X[Start[j]:], X[0]))
                iY.append(np.append(Y[Start[j]:], Y[0]))
                Depths.append(np.append(D[Start[j]:], D[0]))
            Length[j] = iX[j].size
            MaxDepth[j] = np.max(Depths[j])

        # filter based on concave contour length and max depth
        Keep = np.where((Length > 1) & (MaxDepth >= MinDepth))[0]
        Start = Start[Keep]
        Stop = Stop[Keep]
        iX = [iX[Ind].astype(dtype=np.float) for Ind in Keep]
        iY = [iY[Ind].astype(dtype=np.float) for Ind in Keep]
        Depths = [Depths[Ind] for Ind in Keep]

        # attempt cutting if more than 1 sequence is found
        if Start.size > 1:

            # initialize containers to hold cut scores, optimal cut locations
            Scores = np.inf * np.ones((Start.size, Start.size))
            Xcut1 = np.zeros((Start.size, Start.size), dtype=np.uint32)
            Ycut1 = np.zeros((Start.size, Start.size), dtype=np.uint32)
            Xcut2 = np.zeros((Start.size, Start.size), dtype=np.uint32)
            Ycut2 = np.zeros((Start.size, Start.size), dtype=np.uint32)

            # compare candidates pairwise between all runs and score
            for j in np.arange(Start.size):

                # get list of 'j' candidates that pass depth threshold
                jCandidates = np.where(Depths[j] >= MinDepth)[0]

                for k in np.arange(j + 1, Start.size):

                    # get list of 'k' candidates that pass depth threshold
                    kCandidates = np.where(Depths[k] >= MinDepth)[0]

                    # initialize minimum score and cut locations
                    minScore = np.inf
                    minj = -1
                    mink = -1

                    # loop over each coordinate pair for concavities j,k
                    for a in np.arange(jCandidates.size):
                        for b in np.arange(kCandidates.size):

                            # calculate length score
                            Ls = length_score(iX[j][jCandidates[a]],
                                              iY[j][jCandidates[a]],
                                              iX[k][kCandidates[b]],
                                              iY[k][kCandidates[b]],
                                              Depths[j][jCandidates[a]],
                                              Depths[k][kCandidates[b]])

                            # calculate angle score
                            As = angle_score(
                                iX[j][0], iY[j][0], iX[j][-1], iY[j][-1],
                                iX[k][0], iY[k][0], iX[k][-1], iY[k][-1],
                                iX[j][jCandidates[a]], iY[j][jCandidates[a]],
                                iX[k][kCandidates[b]], iY[k][kCandidates[b]])

                            # combine scores
                            Score = (Ls + As) / 2

                            # replace if improvement
                            if Score < minScore:
                                minScore = Score
                                Scores[j, k] = minScore
                                minj = jCandidates[a]
                                mink = kCandidates[b]

                    # record best cut location
                    Xcut1[j, k] = iX[j][minj]
                    Ycut1[j, k] = iY[j][minj]
                    Xcut2[j, k] = iX[k][mink]
                    Ycut2[j, k] = iY[k][mink]

            # pick the best scoring candidates and cut if needed
            ArgMin = np.unravel_index(Scores.argmin(), Scores.shape)
            if Scores[ArgMin[0], ArgMin[1]] <= MinConcavity:

                # perform cut
                SplitMask = cut(Mask, Xcut1[ArgMin[0],
                                            ArgMin[1]].astype(np.float),
                                Ycut1[ArgMin[0], ArgMin[1]].astype(np.float),
                                Xcut2[ArgMin[0], ArgMin[1]].astype(np.float),
                                Ycut2[ArgMin[0], ArgMin[1]].astype(np.float))

                # re-label cut image
                SplitLabel = ms.label(SplitMask)[0]

                # increment object count, and label new object at end
                SplitLabel[SplitLabel > 1] = Total + 1
                Total += 1

                # label object '1' with current object value
                SplitLabel[SplitLabel == 1] = i

                # trim padding from corrected label image
                Mask = Mask[1:-1, 1:-1]
                SplitLabel = SplitLabel[1:-1, 1:-1]

                # update label image
                W[Mask] = SplitLabel[Mask]

            else:  # no cut made, move to next object
                i = i + 1
        else:  # no cuts to attempt, move to next object
            i = i + 1

    return Convex
예제 #6
0
def trace_contours(I, X, Y, Min, Max, MaxLength=255):
    """Performs contour tracing of seed pixels in an intensity image using
    gradient information.

    Parameters
    ----------
    I : array_like
        An intensity image used for analyzing local minima/maxima and
        gradients. Dimensions M x N.
    X : array_like
        A 1D array of horizontal coordinates of contour seed pixels for
        tracing.
    Y : array_like
        A 1D array of the vertical coordinates of seed pixels for tracing.
    Min : array_like
        A 1D array of the corresponding minimum values for contour tracing of
        seed point X, Y.
    Max : array_like
        A 1D array of the corresponding maximum values for contour tracing of
        seed point X, Y.
    MaxLength : int
        Maximum allowable contour length. Default value = 255.

    Notes
    -----
    Can be computationally expensive for large numbers of contours. Use
    smoothing and delta thresholding when seeding contours to reduce burden.

    Returns
    -------
    cXs : list
        A list of 1D numpy arrays defining the horizontal coordinates of object
        boundaries.
    cYs : list
        A list of 1D numpy arrays defining the vertical coordinates of object
        boundaries.

    See Also
    --------
    SeedContours, ScoreContours, MinimumModel

    References
    ----------
    .. [1] S. Weinert et al "Detection and Segmentation of Cell Nuclei in
    Virtual Microscopy Images: A Minimum-Model Approach" in Nature Scientific
    Reports,vol.2,no.503, doi:10.1038/srep00503, 2012.
    """

    # initialize list of lists containing contours
    cXs = []
    cYs = []

    # process each seed pixel sequentially
    for i in np.arange(X.size):

        # capture window surrounding (X[i], Y[i])
        W = I[max(0, Y[i] -
                  np.ceil(MaxLength /
                          2.0)):min(I.shape[0] + 1, Y[i] +
                                    np.ceil(MaxLength / 2.0) + 1),
              max(0, X[i] - np.ceil(MaxLength / 2.0)
                  ):min(I.shape[1] + 1, X[i] + np.ceil(MaxLength / 2.0)) + 1]

        # binary threshold corresponding to seed pixel 'i'
        W = (W <= Max[i]) & (W >= Min[i])

        # embed with center pixel in middle of padded window
        Embed = np.zeros((W.shape[0] + 2, W.shape[1] + 2), dtype=np.bool)
        Embed[1:-1, 1:-1] = W

        # calculate location of (X[i], Y[i]) in 'Embed'
        pX = X[i] - max(0, X[i] - np.ceil(MaxLength / 2.0)) + 1
        pY = Y[i] - max(0, Y[i] - np.ceil(MaxLength / 2.0)) + 1

        # trace boundary, check stopping condition, append to list of contours
        cX, cY = label.trace_boundaries(Embed,
                                        conn=4,
                                        x_start=pX,
                                        y_start=pY,
                                        MaxLength=MaxLength)
        if (cX[0] == cX[-1] and cY[0] == cY[-1] and len(cX) <= MaxLength):

            # add window offset to contour coordinates
            cX = [x + max(0, X[i] - np.ceil(MaxLength / 2.0)) - 1 for x in cX]
            cY = [y + max(0, Y[i] - np.ceil(MaxLength / 2.0)) - 1 for y in cY]

            # append to list of candidate contours
            cXs.append(np.array(cX, dtype=np.uint32))
            cYs.append(np.array(cY, dtype=np.uint32))

    return cXs, cYs