Instruction:
Write a Python 3 script for
returning the convex hull for each label using an ijv labeling pixel_labels and indexes

Response:
def convex_hull_ijv(pixel_labels, indexes, fast=True):
    '''Return the convex hull for each label using an ijv labeling

    pixel_labels: the labeling of the pixels in i,j,v form where
                  i & j are the coordinates of a pixel and v is
                  the pixel's label number
    indexes: the indexes at which to measure the convex hull

    Returns a matrix and a vector. The matrix consists of one row per
    point in the convex hull. Each row has three columns, the label #,
    the i coordinate of the point and the j coordinate of the point. The
    result is organized first by label, then the points are arranged
    counter-clockwise around the perimeter.
    The vector is a vector of #s of points in the convex hull per label
    '''
    if fast:
        return _convex_hull.convex_hull_ijv(pixel_labels, indexes)

    # We keep this code for testing.
    if len(indexes) == 0:
        return np.zeros((0,3),int),np.zeros((0,),int)
    #
    # An array that converts from label # to index in "indexes"
    anti_indexes = np.zeros((np.max(indexes)+1,),int)
    anti_indexes[indexes] = list(range(len(indexes)))

    coords = pixel_labels[:,:2]
    i = coords[:, 0]
    j = coords[:, 1]
    # This disgusting copy spooge appears to be needed for scipy 0.7.0
    labels_per_point = np.zeros(len(pixel_labels), int)
    labels_per_point[:] = pixel_labels[:,2]
    #
    # Calculate the centers for each label
    #
    center_i = fixup_scipy_ndimage_result(
        scind.mean(i.astype(float), labels_per_point, indexes))
    center_j = fixup_scipy_ndimage_result(
        scind.mean(j.astype(float), labels_per_point, indexes))
    centers = np.column_stack((center_i, center_j))
    #
    # Now make an array with one outline point per row and the following
    # columns:
    #
    # index of label # in indexes array
    # angle of the point relative to the center
    # i coordinate of the point
    # j coordinate of the point
    #
    anti_indexes_per_point = anti_indexes[labels_per_point]
    centers_per_point = centers[anti_indexes_per_point]
    angle = np.arctan2(i-centers_per_point[:,0],j-centers_per_point[:,1])
    a = np.zeros((len(i),3), np.int32)
    a[:,0] = anti_indexes_per_point
    a[:,1:] = coords
    #
    # Sort the array first by label # (sort of), then by angle
    #
    order = np.lexsort((angle,anti_indexes_per_point))
    #
    # Make unique
    #
    same_as_next = np.hstack([np.all(a[order[:-1],:] == a[order[1:],:], 1), [False]])
    order = order[~same_as_next]
    a=a[order]
    anti_indexes_per_point = anti_indexes_per_point[order]
    angle = angle[order]
    centers_per_point = centers_per_point[order]
    #
    # Make the result matrix, leaving enough space so that all points might
    # be on the convex hull.
    #
    result = np.zeros((len(order),3), np.int32)
    result[:,0] = labels_per_point[order]
    #
    # Create an initial count vector
    #
    v = np.ones((a.shape[0],),dtype = np.int32)
    result_counts = scipy.sparse.coo_matrix((v,(a[:,0],v*0)),
                                            shape=(len(indexes),1))
    result_counts = result_counts.toarray().flatten()
    r_anti_indexes_per_point = anti_indexes_per_point # save this
    #
    # Create a vector that indexes into the results for each label
    #
    result_index = np.zeros(result_counts.shape, np.int32)
    result_index[1:]=np.cumsum(result_counts[:-1])
    #
    # Initialize the counts of convex hull points to a ridiculous number
    #
    counts = np.iinfo(np.int32).max
    first_pass = True
    while True:
        #
        # Figure out how many putative convex hull points there are for
        # each label.
        #
        # If the count for a label is 3 or less, it's a convex hull or
        # degenerate case.
        #
        # If the count hasn't changed in an iteration, then we've done
        # as well as we can hope to do.
        #
        v = np.ones((a.shape[0],),dtype = np.int32)
        new_counts = scipy.sparse.coo_matrix((v,(a[:,0],v*0)),
                                             shape=(len(indexes),1))
        new_counts = new_counts.toarray().flatten()
        done_count = (2 if first_pass else 3)
        finish_me = ((new_counts > 0) &
                     ((new_counts <= done_count) |
                      (new_counts == counts)))
        indexes_to_finish = np.argwhere(finish_me).astype(np.int32).squeeze()
        keep_me = (new_counts > done_count) & (new_counts < counts)
        indexes_to_keep = np.argwhere(keep_me).astype(np.int32).squeeze()
        if len(indexes_to_finish):
            result_counts[finish_me] = new_counts[finish_me]
            #
            # Store the coordinates of each of the points to finish
            #
            finish_this_row = finish_me[a[:,0]]
            rows_to_finish = np.argwhere(finish_this_row).flatten()
            a_to_finish = a[rows_to_finish]
            atf_indexes = a_to_finish[:,0]
            #
            # Map label #s to the index into indexes_to_finish of that label #
            #
            anti_indexes_to_finish = np.zeros((len(indexes),), np.int32)
            anti_indexes_to_finish[indexes_to_finish] = list(range(len(indexes_to_finish)))
            #
            # Figure out the indices of each point in a label to be finished.
            # We figure out how much to subtract for each label, then
            # subtract that much from 0:N to get successive indexes at
            # each label.
            # Then we add the result_index to figure out where to store it
            # in the result table.
            #
            finish_idx_base = np.zeros((len(indexes_to_finish),), np.int32)
            finish_idx_base[1:]=np.cumsum(new_counts[indexes_to_finish])[:-1]
            finish_idx_bases = finish_idx_base[anti_indexes_to_finish[atf_indexes]]
            finish_idx = (np.array(list(range(a_to_finish.shape[0])))-
                          finish_idx_bases)
            finish_idx = finish_idx + result_index[atf_indexes]
            result[finish_idx,1:] = a_to_finish[:,1:]
        if len(indexes_to_keep) == 0:
            break
        #
        # Figure out which points are still available
        #
        rows_to_keep = np.argwhere(keep_me[a[:,0].astype(np.int32)]).flatten()
        rows_to_keep = rows_to_keep.astype(np.int32)
        a = a[rows_to_keep]
        centers_per_point = centers_per_point[rows_to_keep]
        counts = new_counts
        #
        # The rule is that the area of the triangle from the center to
        # point N-1 to point N plus the area of the triangle from the center
        # to point N to point N+1 must be greater than the area of the
        # triangle from the center to point N-1 to point N+1 for a point
        # to be on the convex hull.
        # N-1 and N+1 have to be modulo "counts", so we make special arrays
        # to address those situations.
        #
        anti_indexes_to_keep = np.zeros((len(indexes),), np.int32)
        anti_indexes_to_keep[indexes_to_keep] = list(range(len(indexes_to_keep)))
        idx_base = np.zeros((len(indexes_to_keep),), np.int32)
        idx_base[1:]=np.cumsum(counts[keep_me])[0:-1]
        idx_bases = idx_base[anti_indexes_to_keep[a[:,0]]]
        counts_per_pt = counts[a[:,0]]
        idx = np.array(list(range(a.shape[0])), np.int32)-idx_bases
        n_minus_one = np.mod(idx+counts_per_pt-1,counts_per_pt)+idx_bases
        n_plus_one  = np.mod(idx+1,counts_per_pt)+idx_bases
        #
        # Compute the triangle areas
        #
        t_left_plus_right = (triangle_areas(centers_per_point,
                                            a[n_minus_one, 1:],
                                            a[:, 1:]) +
                             triangle_areas(centers_per_point,
                                            a[:, 1:],
                                            a[n_plus_one, 1:]))
        t_lr = triangle_areas(centers_per_point,
                              a[n_minus_one, 1:], a[n_plus_one, 1:])
        #
        # Keep the points where the area of the left triangle plus the
        # area of the right triangle is bigger than the area of the triangle
        # composed of the points to the left and right. This means that
        # there's a little triangle sitting on top of t_lr with our point
        # on top and convex in relation to its neighbors.
        #
        keep_me = t_left_plus_right > t_lr
        #
        # If all points on a line are co-linear with the center, then the
        # whole line goes away. Special handling for this to find the points
        # most distant from the center and on the same side
        #
        consider_me = t_left_plus_right == 0
        del t_lr, t_left_plus_right
        if np.any(consider_me):
            diff_i = a[:,1]-centers_per_point[:,0]
            diff_j = a[:,2]-centers_per_point[:,1]
            #
            # The manhattan distance is good enough
            #
            dist = np.abs(diff_i)+np.abs(diff_j)
            # The sign is different on different sides of a line including
            # the center. Multiply j by 2 to keep from colliding with i
            #
            # If both signs are zero, then the point is in the center
            #
            # Compute np.sign(diff_i, out=diff_i) + np.sign(diff_j) * 2,
            # but without any temporaries.
            sign = np.sign(diff_j, diff_j)
            sign *= 2
            sign += np.sign(diff_i)
            del diff_i, diff_j

            n_minus_one_consider = n_minus_one[consider_me]
            n_plus_one_consider = n_plus_one[consider_me]
            left_is_worse = ((dist[consider_me] > dist[n_minus_one_consider]) |
                             (sign[consider_me] != sign[n_minus_one_consider]))
            right_is_worse = ((dist[consider_me] > dist[n_plus_one_consider]) |
                              (sign[consider_me] != sign[n_plus_one_consider]))
            to_keep = left_is_worse & right_is_worse & (sign[consider_me] != 0)
            keep_me[consider_me] = to_keep
            del dist, sign
        a = a[keep_me,:]
        centers_per_point = centers_per_point[keep_me]
        del keep_me
        first_pass = False
    #
    # Finally, we have to shrink the results. We number each of the
    # points for a label, then only keep those whose indexes are
    # less than the count for their label.
    #
    within_label_index = np.array(list(range(result.shape[0])), np.int32)
    counts_per_point = result_counts[r_anti_indexes_per_point]
    result_indexes_per_point = result_index[r_anti_indexes_per_point]
    within_label_index = (within_label_index - result_indexes_per_point)
    result = result[within_label_index < counts_per_point]
    return result, result_counts


Instruction:
Write a Python 3 function that can
fill the convex hulls of objects in the object tree.

Response:
def fill_convex_hulls(ch_pts, ch_counts):
    '''Return the points within the convex hulls of objects

    ch_pts - a Nx3 array of columns of label #, i and j as output by convex_hull
    ch_counts - the number of points per object

    returns the points in ijv format.
    '''
    if len(ch_pts) == 0:
        return np.zeros((0,3), int)
    #
    # Connect the last line to the first
    #
    ch_counts = ch_counts[ch_counts != 0]
    indexer_in = Indexes(ch_counts)
    #
    # Get line ends. This involves connecting the last
    # point with the first.
    #
    idx_1 = np.arange(ch_pts.shape[0]) + 1
    idx_1[indexer_in.fwd_idx+indexer_in.counts - 1] = indexer_in.fwd_idx
    ch_pts1 = ch_pts[idx_1, 1:]
    #
    # Get J at each I (floating point) for each line
    # Generate two points (min J, max J) for horizontal lines
    #
    n_i = np.abs(ch_pts[:, 1] - ch_pts1[:, 0]) + 1
    horizontal = n_i == 1
    if (np.any(horizontal)):
        i_horiz = ch_pts[horizontal, 1]
        l_horiz = ch_pts[horizontal, 0]
        j_first_horiz = ch_pts[horizontal, 2]
        j_last_horiz = ch_pts1[horizontal, 1]
    else:
        i_horiz = l_horiz = j_first_horiz = j_last_horiz = np.zeros(0, int)

    if (np.any(~ horizontal)):
        ch_pts, ch_pts1, n_i = [x[~horizontal] for x in (ch_pts, ch_pts1, n_i)]
        indexer = Indexes(n_i)
        l = ch_pts[indexer.rev_idx, 0]
        sign = np.sign(ch_pts1[:, 0] - ch_pts[:, 1])
        i = ch_pts[indexer.rev_idx, 1] + sign[indexer.rev_idx] * indexer.idx[0]
        j = ch_pts[indexer.rev_idx, 2] + \
            indexer.idx[0] * (ch_pts1[indexer.rev_idx, 1] -
                              ch_pts[indexer.rev_idx, 2]).astype(float) / \
            (n_i[indexer.rev_idx] - 1)
    else:
        i = l = np.zeros(0, int)
        j = np.zeros(0)
    line_labels = np.concatenate((l_horiz, l_horiz, l))
    i = np.concatenate((i_horiz, i_horiz, i))
    j = np.concatenate((j_first_horiz.astype(float),
                        j_last_horiz.astype(float), j))
    #
    # Sort by label, then i, then j
    #
    order = np.lexsort((j, i, line_labels))
    i, j, line_labels = i[order], j[order], line_labels[order]
    #
    # Find the min and max j for every i and label. Then we
    # can make runs of them.
    #
    firsts = np.hstack(
        [[0],
         np.argwhere((line_labels[:-1] != line_labels[1:]) |
                     (i[:-1] != i[1:])).flatten() + 1,
         [len(line_labels)]])
    counts = firsts[1:] - firsts[:-1]
    run_indexer = Indexes(counts)
    #
    # Find the first and last J for each i and create rasters of j
    #
    i = i[run_indexer.fwd_idx]
    j0 = np.ceil(j[run_indexer.fwd_idx]).astype(int)
    j1 = np.floor(j[run_indexer.fwd_idx + run_indexer.counts[0]-1]).astype(int)
    l = line_labels[run_indexer.fwd_idx]
    out_indexer = Indexes(j1-j0+1)
    ijv = np.column_stack((
        i[out_indexer.rev_idx], j0[out_indexer.rev_idx]+out_indexer.idx[0],
        l[out_indexer.rev_idx]))
    return ijv


Instruction:
How would you implement a function in Python 3 that
draws a line between two points pt0 and pt1.

Response:
def draw_line(labels,pt0,pt1,value=1):
    """Draw a line between two points

    pt0, pt1 are in i,j format which is the reverse of x,y format
    Uses the Bresenham algorithm
    Some code transcribed from http://www.cs.unc.edu/~mcmillan/comp136/Lecture6/Lines.html
    """
    y0,x0 = pt0
    y1,x1 = pt1
    diff_y = abs(y1-y0)
    diff_x = abs(x1-x0)
    x = x0
    y = y0
    labels[y,x]=value
    step_x = (x1 > x0 and 1) or -1
    step_y = (y1 > y0 and 1) or -1
    if diff_y > diff_x:
        # Y varies fastest, do x before y
        remainder = diff_x*2 - diff_y
        while y != y1:
            if remainder >= 0:
                x += step_x
                remainder -= diff_y*2
            y += step_y
            remainder += diff_x*2
            labels[y,x] = value
    else:
        remainder = diff_y*2 - diff_x
        while x != x1:
            if remainder >= 0:
                y += step_y
                remainder -= diff_x*2
            x += step_x
            remainder += diff_y*2
            labels[y,x] = value


Instruction:
Write a Python 3 script to
retrieve the coordinates of the points along lines in a single line.

Response:
def get_line_pts(pt0i, pt0j, pt1i, pt1j):
    '''Retrieve the coordinates of the points along lines

    pt0i, pt0j - the starting coordinates of the lines (1-d nparray)
    pt1i, pt1j - the ending coordinates of the lines (1-d nparray)

    use the Bresenham algorithm to find the coordinates along the lines
    connectiong pt0 and pt1. pt01, pt0j, pt1i and pt1j must be 1-d arrays
    of similar size and must be of integer type.

    The results are returned as four vectors - index, count, i, j.
    index is the index of the first point in the line for each coordinate pair
    count is the # of points in the line
    i is the I coordinates for each point
    j is the J coordinate for each point
    '''
    assert len(pt0i) == len(pt0j)
    assert len(pt0i) == len(pt1i)
    assert len(pt0i) == len(pt1j)
    pt0i = np.array(pt0i, int)
    pt0j = np.array(pt0j, int)
    pt1i = np.array(pt1i, int)
    pt1j = np.array(pt1j, int)
    if len(pt0i) == 0:
        # Return four zero-length arrays if nothing passed in
        return [np.zeros((0,),int)] * 4
    #
    # The Bresenham algorithm picks the coordinate that varies the most
    # and generates one point for each step in that coordinate. Add one
    # for the start point.
    #
    diff_i = np.abs(pt0i - pt1i)
    diff_j = np.abs(pt0j - pt1j)
    count = np.maximum(diff_i, diff_j).astype(int) + 1
    #
    # The indexes of the ends of the coordinate vectors are at the
    # cumulative sum of the counts. We get to the starts by subtracting
    # the count.
    #
    index = np.cumsum(count) - count
    #
    # Find the step directions per coordinate pair.
    # True = 1, False = 0
    # True * 2 - 1 = 1, False * 2 - 1 = -1
    #
    step_i = (pt1i > pt0i).astype(int) * 2 - 1
    step_j = (pt1j > pt0j).astype(int) * 2 - 1
    #
    # Make arrays to hold the results
    #
    n_pts = index[-1] + count[-1]
    i = np.zeros(n_pts, int)
    j = np.zeros(n_pts, int)
    #
    # Put pt0 into the arrays
    #
    i[index] = pt0i
    j[index] = pt0j
    #
    # # # # # # # # # #
    #
    # Do the points for which I varies most (or it's a tie).
    #
    mask = (diff_i >= diff_j)
    count_t = count[mask]
    if len(count_t) > 0:
        last_n = np.max(count_t)
        diff_i_t = diff_i[mask]
        diff_j_t = diff_j[mask]
        remainder = diff_j_t * 2 - diff_i_t
        current_i = pt0i[mask]
        current_j = pt0j[mask]
        index_t = index[mask]
        step_i_t = step_i[mask]
        step_j_t = step_j[mask]
        for n in range(1,last_n+1):
            #
            # Eliminate all points that are done
            #
            mask = (count_t > n)
            remainder = remainder[mask]
            current_i = current_i[mask]
            current_j = current_j[mask]
            index_t = index_t[mask]
            count_t = count_t[mask]
            diff_i_t = diff_i_t[mask]
            diff_j_t = diff_j_t[mask]
            step_i_t = step_i_t[mask]
            step_j_t = step_j_t[mask]
            #
            # Take a step in the J direction if the remainder is positive
            #
            remainder_mask = (remainder >= 0)
            current_j[remainder_mask] += step_j_t[remainder_mask]
            remainder[remainder_mask] -= diff_i_t[remainder_mask] * 2
            #
            # Always take a step in the I direction
            #
            current_i += step_i_t
            remainder += diff_j_t * 2
            i[index_t+n] = current_i
            j[index_t+n] = current_j
    #
    # # # # # # # # # #
    #
    # Do the points for which J varies most
    #
    mask = (diff_j > diff_i)
    count_t = count[mask]
    if len(count_t) > 0:
        last_n = np.max(count_t)
        diff_i_t = diff_i[mask]
        diff_j_t = diff_j[mask]
        remainder = diff_i_t * 2 - diff_j_t
        current_i = pt0i[mask]
        current_j = pt0j[mask]
        index_t = index[mask]
        step_i_t = step_i[mask]
        step_j_t = step_j[mask]
        for n in range(1,last_n+1):
            #
            # Eliminate all points that are done
            #
            mask = (count_t > n)
            remainder = remainder[mask]
            current_i = current_i[mask]
            current_j = current_j[mask]
            index_t = index_t[mask]
            count_t = count_t[mask]
            diff_i_t = diff_i_t[mask]
            diff_j_t = diff_j_t[mask]
            step_i_t = step_i_t[mask]
            step_j_t = step_j_t[mask]
            #
            # Take a step in the I direction if the remainder is positive
            #
            remainder_mask = (remainder >= 0)
            current_i[remainder_mask] += step_i_t[remainder_mask]
            remainder[remainder_mask] -= diff_j_t[remainder_mask] * 2
            #
            # Always take a step in the J direction
            #
            current_j += step_j_t
            remainder += diff_i_t * 2
            i[index_t+n] = current_i
            j[index_t+n] = current_j
    return index, count, i, j


Instruction:
Create a Python 3 function to
convert a series of polygon lines to a binary array of the given shape.

Response:
def polygon_lines_to_mask(pt0i, pt0j, pt1i, pt1j, shape):
    '''Convert a series of polygon lines to a background / foreground mask

    pt0i, pt0j, pt1i, pt1j - start / end points of lines. Points are rounded
                             to the nearest integer coordinate if float.

    shape - shape of the mask array

    This algorithm assumes that the lines form closed polygons. The lines
    can be in any desired order.
    returns a binary array of the given shape with the interiors of
    polygons filled in.

    The algorithm assumes that polygons do not overlap. Overlapping polygons
    should be evaluated separately and their results should be or'ed
    '''
    #
    # Use get_line_pts to find the points along the lines. Mark all of
    # these points as within the mask.
    #
    # Add all points along all lines, except for those whose i coordinate
    # is at the minimum for that line. This avoids doubly counting in cases
    # such as this:
    #
    #   O
    #   | \
    #   O  0
    #   | /
    #   O
    #
    # and this
    #
    #         O
    #         |\
    #     O---0 \
    #
    # Also remove all points in a line at a particular i but the last to
    # correct for this:
    #
    #    0
    #    | + +
    #    |     + + <- count = 3
    #    0 + + + + 0
    #
    # Use a sparse matrix to sum the hits at every pixel. Some pixels will have
    # double hits:
    #
    #  1       1
    #    1   1
    #      2
    #
    # Also, if two polygons share a common side, that side will be doubly
    # numbered and will disappear.
    #
    #Pixels with odd numbers are foreground and with
    # even numbers are background.
    #
    #  1 1 1 1 2 <- 2 already included because it's on a line.
    #    1 1 2
    #      2
    #
    pt0i, pt0j, pt1i, pt1j = [np.round(np.atleast_1d(x)).astype(int)
                              for x in (pt0i, pt0j, pt1i, pt1j)]
    result = np.zeros(shape, bool)
    discard_me = pt0i == pt1i
    index, count, i, j = get_line_pts(pt0i[discard_me], pt0j[discard_me],
                                      pt1i[discard_me], pt1j[discard_me])
    result[i, j] = True
    pt0i, pt0j, pt1i, pt1j = [x[~discard_me] for x in (pt0i, pt0j, pt1i, pt1j)]
    reverse_me = pt0i > pt1i
    pt0i[reverse_me], pt0j[reverse_me], pt1i[reverse_me], pt1j[reverse_me] = \
        pt1i[reverse_me], pt1j[reverse_me], pt0i[reverse_me], pt0j[reverse_me]
    index, count, i, j = get_line_pts(pt0i, pt0j, pt1i, pt1j)
    #
    # Mark all i,j as foreground
    #
    result[i, j] = True
    #
    # Remove any pixel whose I is pt0i
    #
    indexer = Indexes(count)
    dont_use = pt0i[indexer.rev_idx] == i
    #
    # Remove any pixel which is from the same line as the next pixel
    # and has the same "i"
    #
    dont_use[:-1] |= ((indexer.rev_idx[:-1] == indexer.rev_idx[1:]) &
                      (i[:-1] == i[1:]))
    i, j = i[~ dont_use], j[~ dont_use]
    #
    # Create a sparse matrix to do the counting, then convert.
    #
    hitcounts = scipy.sparse.coo_matrix((np.ones(len(i), int), (i, j)),
                                        shape = shape,
                                        dtype = np.uint16).toarray()
    #
    # Scan along the I direction. Odd pixels are inside.
    #
    result[(np.cumsum(hitcounts, 1) % 2) == 1] = True
    return result


Instruction:
Make a summary of the following Python 3 code
def fixup_scipy_ndimage_result(whatever_it_returned):
    if getattr(whatever_it_returned,"__getitem__",False):
        return np.array(whatever_it_returned)
    else:
        return np.array([whatever_it_returned])

Response: Convert a result from scipy. ndimage to a numpy array


Instruction:
How would you explain what the following Python 3 function does
def centers_of_labels(labels):
    '''Return the i,j coordinates of the centers of a labels matrix

    The result returned is an 2 x n numpy array where n is the number
    of the label minus one, result[0,x] is the i coordinate of the center
    and result[x,1] is the j coordinate of the center.
    You can unpack the result as "i,j = centers_of_labels(labels)"
    '''
    max_labels = np.max(labels)
    if max_labels == 0:
        return np.zeros((2,0),int)

    result = scind.center_of_mass(np.ones(labels.shape),
                                  labels,
                                  np.arange(max_labels)+1)
    result = np.array(result)
    if result.ndim == 1:
        result.shape = (2,1)
        return result
    return result.transpose()

Response: Return the i j coordinates of the centers of a set of labels.


Instruction:
Can you write a function in Python 3 where it
returns the i j coordinates of the maximum value within each object within each label.

Response:
def maximum_position_of_labels(image, labels, indices):
    '''Return the i,j coordinates of the maximum value within each object

    image - measure the maximum within this image
    labels - use the objects within this labels matrix
    indices - label #s to measure

    The result returned is an 2 x n numpy array where n is the number
    of the label minus one, result[0,x] is the i coordinate of the center
    and result[x,1] is the j coordinate of the center.
    '''

    if len(indices) == 0:
        return np.zeros((2,0),int)

    result = scind.maximum_position(image, labels, indices)
    result = np.array(result,int)
    if result.ndim == 1:
        result.shape = (2,1)
        return result
    return result.transpose()


Instruction:
Explain what the following Python 3 code does
def minimum_enclosing_circle(labels, indexes = None,
                             hull_and_point_count = None):
    """Find the location of the minimum enclosing circle and its radius

    labels - a labels matrix
    indexes - an array giving the label indexes to be processed
    hull_and_point_count - convex_hull output if already done. None = calculate

    returns an Nx3 array organized as i,j of the center and radius
    Algorithm from
    http://www.personal.kent.edu/~rmuhamma/Compgeometry/MyCG/CG-Applets/Center/centercli.htm
    who calls it the Applet's Algorithm and ascribes it to Pr. Chrystal
    The original citation is Professor Chrystal, "On the problem to construct
    the minimum circle enclosing n given points in a plane", Proceedings of
    the Edinburgh Mathematical Society, vol 3, 1884
    """
    if indexes is None:
        if hull_and_point_count is not None:
            indexes = np.array(np.unique(hull_and_point_count[0][:,0]),dtype=np.int32)
        else:
            max_label = np.max(labels)
            indexes = np.array(list(range(1,max_label+1)),dtype=np.int32)
    else:
        indexes = np.array(indexes,dtype=np.int32)
    if indexes.shape[0] == 0:
        return np.zeros((0,2)),np.zeros((0,))

    if hull_and_point_count is None:
        hull, point_count = convex_hull(labels, indexes)
    else:
        hull, point_count = hull_and_point_count
    centers = np.zeros((len(indexes),2))
    radii = np.zeros((len(indexes),))
    #
    # point_index is the index to the first point in "hull" for a label
    #
    point_index = np.zeros((indexes.shape[0],),int)
    point_index[1:] = np.cumsum(point_count[:-1])
    #########################################################################
    #
    # The algorithm is this:
    # * Choose a line S from S0 to S1 at random from the set of adjacent
    #   S0 and S1
    # * For every vertex (V) other than S, compute the angle from S0
    #   to V to S. If this angle is obtuse, the vertex V lies within the
    #   minimum enclosing circle and can be ignored.
    # * Find the minimum angle for all V.
    #   If the minimum angle is obtuse, stop and accept S as the diameter of
    #   the circle.
    # * If the vertex with the minimum angle makes angles S0-S1-V and
    #   S1-S0-V that are acute and right, then take S0, S1 and V as the
    #   triangle within the circumscribed minimum enclosing circle.
    # * Otherwise, find the largest obtuse angle among S0-S1-V and
    #   S1-S0-V (V is the vertex with the minimum angle, not all of them).
    #   If S0-S1-V is obtuse, make V the new S1, otherwise make V the new S0
    #
    ##########################################################################
    #
    # anti_indexes is used to transform a label # into an index in the above array
    # anti_indexes_per_point gives the label index of any vertex
    #
    anti_indexes=np.zeros((np.max(indexes)+1,),int)
    anti_indexes[indexes] = list(range(indexes.shape[0]))
    anti_indexes_per_point = anti_indexes[hull[:,0]]
    #
    # Start out by eliminating the degenerate cases: 0, 1 and 2
    #
    centers[point_count==0,:]= np.NaN
    if np.all(point_count == 0):
        # Bail if there are no points in any hull to prevent
        # index failures below.
        return centers,radii

    centers[point_count==1,:]=hull[point_index[point_count==1],1:]
    radii[point_count < 2]=0
    centers[point_count==2,:]=(hull[point_index[point_count==2],1:]+
                               hull[point_index[point_count==2]+1,1:])/2
    distance = centers[point_count==2,:] - hull[point_index[point_count==2],1:]
    radii[point_count==2]=np.sqrt(distance[:,0]**2+distance[:,1]**2)
    #
    # Get rid of the degenerate points
    #
    keep_me = point_count > 2
    #
    # Pick S0 as the first point in each label
    # and S1 as the second.
    #
    s0_idx = point_index.copy()
    s1_idx = s0_idx+1
    #
    # number each of the points in a label with an index # which gives
    # the order in which we'll get their angles. We use this to pick out
    # points # 2 to N which are the candidate vertices to S
    #
    within_label_indexes = (np.array(list(range(hull.shape[0])),int) -
                            point_index[anti_indexes_per_point])

    while(np.any(keep_me)):
        #############################################################
        # Label indexing for active labels
        #############################################################
        #
        # labels_to_consider contains the labels of the objects which
        # have not been completed
        #
        labels_to_consider = indexes[keep_me]
        #
        # anti_indexes_to_consider gives the index into any vector
        # shaped similarly to labels_to_consider (for instance, min_angle
        # below) for every label in labels_to_consider.
        #
        anti_indexes_to_consider =\
            np.zeros((np.max(labels_to_consider)+1,),int)
        anti_indexes_to_consider[labels_to_consider] = \
            np.array(list(range(labels_to_consider.shape[0])))
        ##############################################################
        # Vertex indexing for active vertexes other than S0 and S1
        ##############################################################
        #
        # The vertices are hull-points with indexes of 2 or more
        # keep_me_vertices is a mask of the vertices to operate on
        # during this iteration
        #
        keep_me_vertices = np.logical_and(keep_me[anti_indexes_per_point],
                                             within_label_indexes >= 2)
        #
        # v is the vertex coordinates for each vertex considered
        #
        v  = hull[keep_me_vertices,1:]
        #
        # v_labels is the label from the label matrix for each vertex
        #
        v_labels = hull[keep_me_vertices,0]
        #
        # v_indexes is the index into "hull" for each vertex (and similarly
        # shaped vectors such as within_label_indexes
        #
        v_indexes=np.argwhere(keep_me_vertices).flatten().astype(np.int32)
        #
        # anti_indexes_per_vertex gives the index into "indexes" and
        # any similarly shaped array of per-label values
        # (for instance s0_idx) for each vertex being considered
        #
        anti_indexes_per_vertex = anti_indexes_per_point[keep_me_vertices]
        #
        # anti_indexes_to_consider_per_vertex gives the index into any
        # vector shaped similarly to labels_to_consider for each
        # vertex being analyzed
        #
        anti_indexes_to_consider_per_vertex = anti_indexes_to_consider[v_labels]
        #
        # Get S0 and S1 per vertex
        #
        s0 = hull[s0_idx[keep_me],1:]
        s1 = hull[s1_idx[keep_me],1:]
        s0 = s0[anti_indexes_to_consider_per_vertex]
        s1 = s1[anti_indexes_to_consider_per_vertex]
        #
        # Compute the angle S0-S1-V
        #
        # the first vector of the angles is between S0 and S1
        #
        s01 = (s0 - s1).astype(float)
        #
        # compute V-S1 and V-S0 at each of the vertices to be considered
        #
        vs0 = (v - s0).astype(float)
        vs1 = (v - s1).astype(float)
        #
        #` Take the dot product of s01 and vs1 divided by the length of s01 *
        # the length of vs1. This gives the cosine of the angle between.
        #
        dot_vs1s0 = (np.sum(s01*vs1,1) /
                     np.sqrt(np.sum(s01**2,1)*np.sum(vs1**2,1)))
        angle_vs1s0 = np.abs(np.arccos(dot_vs1s0))
        s10 = -s01
        dot_vs0s1 = (np.sum(s10*vs0,1) /
                     np.sqrt(np.sum(s01**2,1)*np.sum(vs0**2,1)))
        angle_vs0s1 = np.abs(np.arccos(dot_vs0s1))
        #
        # S0-V-S1 is pi - the other two
        #
        angle_s0vs1 = np.pi - angle_vs1s0 - angle_vs0s1
        assert np.all(angle_s0vs1 >= 0)
        #
        # Now we find the minimum angle per label
        #
        min_angle = scind.minimum(angle_s0vs1,v_labels,
                                  labels_to_consider)
        min_angle = fixup_scipy_ndimage_result(min_angle)
        min_angle_per_vertex = min_angle[anti_indexes_to_consider_per_vertex]
        #
        # Calculate the index into V of the minimum angle per label.
        # Use "indexes" instead of labels_to_consider so we get something
        # with the same shape as keep_me
        #
        min_position = scind.minimum_position(angle_s0vs1,v_labels,
                                              indexes)
        min_position = fixup_scipy_ndimage_result(min_position).astype(int)
        min_position = min_position.flatten()
        #
        # Case 1: minimum angle is obtuse or right. Accept S as the diameter.
        # Case 1a: there are no vertices. Accept S as the diameter.
        #
        vertex_counts = scind.sum(keep_me_vertices,
                                  hull[:,0],
                                  labels_to_consider)
        vertex_counts = fixup_scipy_ndimage_result(vertex_counts)
        case_1 = np.logical_or(min_angle >= np.pi / 2,
                                  vertex_counts == 0)

        if np.any(case_1):
            # convert from a boolean over indexes_to_consider to a boolean
            # over indexes
            finish_me = np.zeros((indexes.shape[0],),bool)
            finish_me[anti_indexes[labels_to_consider[case_1]]] = True
            s0_finish_me = hull[s0_idx[finish_me],1:].astype(float)
            s1_finish_me = hull[s1_idx[finish_me],1:].astype(float)
            centers[finish_me] = (s0_finish_me + s1_finish_me)/2
            radii[finish_me] = np.sqrt(np.sum((s0_finish_me -
                                                     s1_finish_me)**2,1))/2
            keep_me[finish_me] = False
        #
        # Case 2: all angles for the minimum angle vertex are acute
        #         or right.
        #         Pick S0, S1 and the vertex with the
        #         smallest angle as 3 points on the circle. If you look at the
        #         geometry, the diameter is the length of S0-S1 divided by
        #         the cosine of 1/2 of the angle. The center of the circle
        #         is at the circumcenter of the triangle formed by S0, S1 and
        #         V.
        case_2 = keep_me.copy()
        case_2[angle_vs1s0[min_position] > np.pi/2] = False
        case_2[angle_vs0s1[min_position] > np.pi/2] = False
        case_2[angle_s0vs1[min_position] > np.pi/2] = False

        if np.any(case_2):
            #
            # Wikipedia (http://en.wikipedia.org/wiki/Circumcircle#Cartesian_coordinates)
            # gives the following:
            # D = 2(S0y Vx + S1y S0x - S1y Vx - S0y S1x - S0x Vy + S1x Vy)
            # D = 2(S0x (S1y-Vy) + S1x(Vy-S0y) + Vx(S0y-S1y)
            # x = ((S0x**2+S0y**2)(S1y-Vy)+(S1x**2+S1y**2)(Vy-S0y)+(Vx**2+Vy**2)(S0y-S1y)) / D
            # y = ((S0x**2+S0y**2)(Vx-S1x)+(S1x**2+S1y**2)(S0y-Vy)+(Vx**2+Vy**2)(S1y-S0y)) / D
            #
            ss0 = hull[s0_idx[case_2],1:].astype(float)
            ss1 = hull[s1_idx[case_2],1:].astype(float)
            vv  = v[min_position[case_2]].astype(float)
            Y = 0
            X = 1
            D = 2*(ss0[:,X] * (ss1[:,Y] - vv[:,Y]) +
                   ss1[:,X] * (vv[:,Y]  - ss0[:,Y]) +
                   vv[:,X]  * (ss0[:,Y] - ss1[:,Y]))
            x = (np.sum(ss0**2,1)*(ss1[:,Y] - vv[:,Y]) +
                 np.sum(ss1**2,1)*(vv[:,Y]  - ss0[:,Y]) +
                 np.sum(vv**2,1) *(ss0[:,Y] - ss1[:,Y])) / D
            y = (np.sum(ss0**2,1)*(vv[:,X]  - ss1[:,X]) +
                 np.sum(ss1**2,1)*(ss0[:,X] - vv[:,X]) +
                 np.sum(vv**2,1) *(ss1[:,X] - ss0[:,X])) / D
            centers[case_2,X] = x
            centers[case_2,Y] = y
            distances = ss0-centers[case_2]
            radii[case_2] = np.sqrt(np.sum(distances**2,1))
            keep_me[case_2] = False
        #
        # Finally, for anybody who's left, for each of S0-S1-V and
        # S1-S0-V, for V, the vertex with the minimum angle,
        # find the largest obtuse angle. The vertex of this
        # angle (S0 or S1) is inside the enclosing circle, so take V
        # and either S1 or S0 as the new S.
        #
        # This involves a relabeling of within_label_indexes. We replace
        # either S0 or S1 with V and assign V either 0 or 1
        #
        if np.any(keep_me):
            labels_to_consider = indexes[keep_me]
            indexes_to_consider = anti_indexes[labels_to_consider]
            #
            # Index into within_label_indexes for each V with the
            # smallest angle
            #
            v_obtuse_indexes = v_indexes[min_position[keep_me]]
            angle_vs0s1_to_consider = angle_vs0s1[min_position[keep_me]]
            angle_vs1s0_to_consider = angle_vs1s0[min_position[keep_me]]
            #
            # Do the cases where S0 is larger
            #
            s0_is_obtuse = angle_vs0s1_to_consider > np.pi/2
            if np.any(s0_is_obtuse):
                #
                # The index of the obtuse S0
                #
                v_obtuse_s0_indexes = v_obtuse_indexes[s0_is_obtuse]
                obtuse_s0_idx = s0_idx[indexes_to_consider[s0_is_obtuse]]
                #
                # S0 gets the within_label_index of the vertex
                #
                within_label_indexes[obtuse_s0_idx] = \
                    within_label_indexes[v_obtuse_s0_indexes]
                #
                # Assign V as the new S0
                #
                s0_idx[indexes_to_consider[s0_is_obtuse]] = v_obtuse_s0_indexes
                within_label_indexes[v_obtuse_s0_indexes] = 0
            #
            # Do the cases where S1 is larger
            #
            s1_is_obtuse = np.logical_not(s0_is_obtuse)
            if np.any(s1_is_obtuse):
                #
                # The index of the obtuse S1
                #
                v_obtuse_s1_indexes = v_obtuse_indexes[s1_is_obtuse]
                obtuse_s1_idx = s1_idx[indexes_to_consider[s1_is_obtuse]]
                #
                # S1 gets V's within_label_index and goes onto the list
                # of considered vertices.
                #
                within_label_indexes[obtuse_s1_idx] = \
                    within_label_indexes[v_obtuse_s1_indexes]
                #
                # Assign V as the new S1
                #
                s1_idx[indexes_to_consider[s1_is_obtuse]] = v_obtuse_s1_indexes
                within_label_indexes[v_obtuse_s1_indexes] = 1
    return centers, radii

Response: This function returns the location of the minimum enclosing circle and its radius


Instruction:
Can you generate a brief explanation for the following Python 3 code
def associate_by_distance(labels_a, labels_b, distance):
    '''Find the objects that are within a given distance of each other

    Given two labels matrices and a distance, find pairs of objects that
    are within the given distance of each other where the distance is
    the minimum distance between any point in the convex hull of the
    two objects.

    labels_a - first labels matrix
    labels_b - second labels matrix
    distance - distance to measure

    returns a n x 2 matrix where m[x,0] is the label number in labels1 and
    m[x,1] is the label number in labels2

    Algorithm for computing distance between convex polygons taken from
    Chin, "Optimal Algorithms for the Intersection and the Minimum Distance
    Problems Between Planar Polygons", IEEE Transactions on Computers,
    vol. C-32, # 12, December 1983
    '''
    if np.max(labels_a) == 0 or np.max(labels_b) == 0:
        return np.zeros((0,2),int)

    hull_a, point_counts_a = convex_hull(labels_a)
    hull_b, point_counts_b = convex_hull(labels_b)
    centers_a, radii_a = minimum_enclosing_circle(
        labels_a, hull_and_point_count = (hull_a, point_counts_a))
    centers_b, radii_b = minimum_enclosing_circle(
        labels_b, hull_and_point_count = (hull_b, point_counts_b))
    #
    # Make an indexer into the hull tables
    #
    indexer_a = np.cumsum(point_counts_a)
    indexer_a[1:] = indexer_a[:-1]
    indexer_a[0] = 0
    indexer_b = np.cumsum(point_counts_b)
    indexer_b[1:] = indexer_b[:-1]
    indexer_b[0] = 0
    #
    # Compute the distances between minimum enclosing circles =
    # distance - radius_a - radius_b
    #
    i,j = np.mgrid[0:len(radii_a),0:len(radii_b)]
    ab_distance = np.sqrt((centers_a[i,0]-centers_b[j,0])**2 +
                          (centers_a[i,1]-centers_b[j,1])**2)
    ab_distance_minus_radii = ab_distance - radii_a[i] - radii_b[j]
    # Account for roundoff error
    ab_distance_minus_radii -= np.sqrt(np.finfo(float).eps)
    #
    # Exclude from consideration ab_distance > distance and automatically
    # choose those whose centers are within the distance
    #
    ab_easy_wins = ab_distance <= distance
    ij_wins = np.dstack((hull_a[indexer_a[i[ab_easy_wins]],0],
                         hull_b[indexer_b[j[ab_easy_wins]],0]))
    ij_wins.shape = ij_wins.shape[1:]

    ab_consider = (ab_distance_minus_radii <= distance) & (~ ab_easy_wins)
    ij_consider = np.dstack((i[ab_consider], j[ab_consider]))
    ij_consider.shape = ij_consider.shape[1:]
    if np.product(ij_consider.shape) == 0:
        return ij_wins
    if True:
        wins = []
        distance2 = distance**2
        for ii,jj in ij_consider:
            a = hull_a[indexer_a[ii]:indexer_a[ii]+point_counts_a[ii],1:]
            b = hull_b[indexer_b[jj]:indexer_b[jj]+point_counts_b[jj],1:]
            d = minimum_distance2(a,centers_a[ii,:],
                                  b,centers_b[jj,:])
            if d <= distance2:
                wins.append((hull_a[indexer_a[ii],0],
                             hull_b[indexer_b[jj],0]))
        ij_wins = np.vstack((ij_wins, np.array(wins)))
        return ij_wins
    else:
        #
        # For each point in the hull, get the next point mod # of points in hull
        #
        hull_next_a = np.arange(hull_a.shape[0])+1
        hull_next_a[indexer_a+point_counts_a-1] = indexer_a
        hull_next_b = np.arange(hull_b.shape[0])+1
        hull_next_b[indexer_b+point_counts_b-1] = indexer_b
        #
        # Parallelize the algorithm for overlap
        #
        # For each pair of points i, i+1 mod n in the hull, and the test point t
        # the cross product of the vector from i to i+1 and the vector from i+1
        # to t should have the same sign.
        #
        next_b = hull_b[hull_next_b,1:]
        vector_b = hull_b[:,1:] - next_b
        #
        # For each i,j, we have to compare the centers_a against point_counts_b[j]
        # crosses.
        #
        b_len = point_counts_b[ij_consider[:,1]]
        b_index = np.cumsum(point_counts_b)
        b_elems = b_index[-1]
        b_index[1:] = b_index[:-1]
        b_index[0] = 0
        #
        # First create a vector that's b_elems long and every element contains an
        # index into the ij_consider vector. How we do this:
        # 1) mark the first element at a particular index by 1, all others = 0
        # 2) Erase the first 1
        # 3) Take the cumulative sum which will increment to 1 when it hits the
        #    first 1, again when it hits the second...etc.
        #
        b_indexer = np.zeros(b_elems, int)
        b_indexer[b_index[1:]] = 1
        b_indexer = np.cumsum(b_indexer)
        #
        # The sub-index is the index from 1 to n for each of the vertices
        # per b convex hull
        #
        b_sub_index = np.arange(b_elems) - b_index[b_indexer]
        #
        # For each element of b_indexer, get the i and j at that index
        #
        b_i = ij_consider[b_indexer,0]
        b_j = ij_consider[b_indexer,1]
        #
        # Compute the cross-products now
        #
        b_vector_b = vector_b[indexer_b[b_j]+b_sub_index,:]
        b_center_vector = (next_b[indexer_b[b_j]+b_sub_index,:] -
                           centers_a[indexer_a[b_i]])
        cross = (b_vector_b[:,0] * b_center_vector[:,1] -
                 b_vector_b[:,1] * b_center_vector[:,0])
        hits = (all_true(cross > 0, b_index) | all_true(cross < 0, b_index))

        ij_wins = np.vstack((ij_wins, ij_consider[hits,:]))
        ij_consider = ij_consider[~hits,:]
        if ij_consider.shape[0] == 0:
            return ij_wins

Response: Given two sets of labels and a distance find the pairs of objects that are within a given distance of each other.


Instruction:
Here you have a function in Python 3, explain what it does
def minimum_distance2(hull_a, center_a, hull_b, center_b):
    '''Return the minimum distance or 0 if overlap between 2 convex hulls

    hull_a - list of points in clockwise direction
    center_a - a point within the hull
    hull_b - list of points in clockwise direction
    center_b - a point within the hull
    '''
    if hull_a.shape[0] < 3 or hull_b.shape[0] < 3:
        return slow_minimum_distance2(hull_a, hull_b)
    else:
        return faster_minimum_distance2(hull_a, center_a, hull_b, center_b)

Response: Return the minimum distance between two convex hulls


Instruction:
Write a Python 3 script for
doing the minimum distance by exhaustive examination of all points in a hull and a hull b.

Response:
def slow_minimum_distance2(hull_a, hull_b):
    '''Do the minimum distance by exhaustive examination of all points'''
    d2_min = np.iinfo(int).max
    for a in hull_a:
        if within_hull(a, hull_b):
            return 0
    for b in hull_b:
        if within_hull(b, hull_a):
            return 0
    for pt_a in hull_a:
        for pt_b in hull_b:
            d2_min = min(d2_min, np.sum((pt_a - pt_b)**2))

    for h1, h2 in ((hull_a, hull_b), (hull_b, hull_a)):
        # Find the distance from a vertex in h1 to an edge in h2
        for pt1 in h1:
            prev_pt2 = h2[-1,:]
            for pt2 in h2:
                if (np.dot(pt2-prev_pt2,pt1-prev_pt2) > 0 and
                    np.dot(prev_pt2-pt2,pt1-pt2) > 0):
                    # points form an acute triangle, so edge is closer
                    # than vertices
                    d2_min = min(d2_min, distance2_to_line(pt1, prev_pt2, pt2))
                prev_pt2 = pt2
    return d2_min


Instruction:
Explain what the following Python 3 code does
def faster_minimum_distance2(hull_a, center_a, hull_b, center_b):
    '''Do the minimum distance using the bimodal property of hull ordering

    '''
    #
    # Find the farthest vertex in b from some point within A. Find the
    # vertices within A visible from this point in B. If the point in A
    # is within B or the farthest vertex in B is within A, then the objects
    # intersect.
    #
    if within_hull(center_a, hull_b):
        return 0
    farthest_b = find_farthest(center_a, hull_b)
    if within_hull(hull_b[farthest_b,:], hull_a):
        return 0
    visible_b = find_visible(hull_b, center_a, farthest_b)
    #
    # Do the same for B
    if within_hull(center_b, hull_a):
        return 0
    farthest_a = find_farthest(center_b, hull_a)
    if within_hull(hull_a[farthest_a,:], hull_b):
        return 0
    visible_a = find_visible(hull_a, center_b, farthest_a)
    #
    # Now go from the first in A and last in B measuring distances
    # which should decrease as we move toward the best
    #
    i = visible_a[0]
    i_next = (i+1) % hull_a.shape[0]
    j = visible_b[1]
    j_next = (j+hull_b.shape[0]-1) % hull_b.shape[0]
    a = hull_a[i,:]
    a_next = hull_a[i_next,:]
    b = hull_b[j,:]
    b_next = hull_b[j_next,:]
    d2_min = np.sum((a-b)**2)

    while i != visible_a[1] and j != visible_b[0]:
        if lines_intersect(a, a_next, b, b_next):
            return 0
        if (np.dot(b-b_next,a-b_next) > 0 and
            np.dot(b_next-b,a-b) > 0):
            # do the edge if better than the vertex
            d2a = distance2_to_line(b, a, a_next)
        else:
            # try the next vertex of a
            d2a = np.sum((a_next-b)**2)
        if (np.dot(a-a_next,b-a_next) > 0 and
            np.dot(a_next-a,b-a) > 0):
            d2b = distance2_to_line(a, b, b_next)
        else:
            d2b = np.sum((b_next-a)**2)
        if d2a < d2_min and d2a < d2b:
            # The edge of A is closer than the best or the b-edge
            # Take it and advance A
            d2_min = d2a
            a = a_next
            i = i_next
            i_next = (i+1) % hull_a.shape[0]
            a_next = hull_a[i_next,:]
        elif d2b < d2_min:
            # B is better. Take it and advance
            d2_min = d2b
            b = b_next
            j = j_next
            j_next = (j+hull_b.shape[0]-1) % hull_b.shape[0]
            b_next = hull_b[j_next,:]
        else:
            return d2_min
    #
    # Some more to do... either one more i or one more j
    #
    while i != visible_a[1]:
        d2_min = min(d2_min, np.sum((a_next-b)**2))
        a = a_next
        i = i_next
        i_next = (i+1) % hull_a.shape[0]
        a_next = hull_a[i_next,:]

    while j != visible_b[0]:
        d2_min = min(d2_min, np.sum((b_next-a)**2))
        b = b_next
        j = j_next
        j_next = (j+ hull_b.shape[0]-1) % hull_b.shape[0]
        b_next = hull_b[j_next,:]
    return d2_min

Response: Do the minimum distance using the bimodal property of hull ordering


Instruction:
Can you tell what is the following Python 3 function doing
def find_farthest(point, hull):
    '''Find the vertex in hull farthest away from a point'''
    d_start = np.sum((point-hull[0,:])**2)
    d_end = np.sum((point-hull[-1,:])**2)
    if d_start > d_end:
        # Go in the forward direction
        i = 1
        inc = 1
        term = hull.shape[0]
        d2_max = d_start
    else:
        # Go in the reverse direction
        i = hull.shape[0]-2
        inc = -1
        term = -1
        d2_max = d_end
    while i != term:
        d2 = np.sum((point - hull[i,:])**2)
        if d2 < d2_max:
            break
        i += inc
        d2_max = d2
    return i-inc

Response: Find the vertex in hull farthest away from a point


Instruction:
Write a Python 3 script for
giving an observer location find the first and last visible AttributeNames that are in the hull and the observer at the observer.

Response:
def find_visible(hull, observer, background):
    '''Given an observer location, find the first and last visible
       points in the hull

       The observer at "observer" is looking at the hull whose most distant
       vertex from the observer is "background. Find the vertices that are
       the furthest distance from the line between observer and background.
       These will be the start and ends in the vertex chain of vertices
       visible by the observer.
       '''
    pt_background = hull[background,:]
    vector = pt_background - observer
    i = background
    dmax = 0
    while True:
        i_next = (i+1) % hull.shape[0]
        pt_next = hull[i_next,:]
        d = -np.cross(vector, pt_next-pt_background)
        if d < dmax or i_next == background:
            i_min = i
            break
        dmax = d
        i = i_next
    dmax = 0
    i = background
    while True:
        i_next = (i+hull.shape[0]-1) % hull.shape[0]
        pt_next = hull[i_next,:]
        d = np.cross(vector, pt_next-pt_background)
        if d < dmax or i_next == background:
            i_max = i
            break
        dmax = d
        i = i_next
    return (i_min, i_max)


Instruction:
How would you explain what the following Python 3 function does
def distance2_to_line(pt, l0, l1):
    '''The perpendicular distance squared from a point to a line

    pt - point in question
    l0 - one point on the line
    l1 - another point on the line
    '''
    pt = np.atleast_1d(pt)
    l0 = np.atleast_1d(l0)
    l1 = np.atleast_1d(l1)
    reshape = pt.ndim == 1
    if reshape:
        pt.shape = l0.shape = l1.shape = (1, pt.shape[0])
    result = (((l0[:,0] - l1[:,0]) * (l0[:,1] - pt[:,1]) -
               (l0[:,0] - pt[:,0]) * (l0[:,1] - l1[:,1]))**2 /
              np.sum((l1-l0)**2, 1))
    if reshape:
        result = result[0]
    return result

Response: The perpendicular distance squared from a point to a line.


Instruction:
Can you tell what is the following Python 3 function doing
def within_hull(point, hull):
    '''Return true if the point is within the convex hull'''
    h_prev_pt = hull[-1,:]
    for h_pt in hull:
        if np.cross(h_pt-h_prev_pt, point - h_pt) >= 0:
            return False
        h_prev_pt = h_pt
    return True

Response: Return True if the point is within the convex hull.


Instruction:
Given the following Python 3 function, write the documentation
def all_true(a, indexes):
    '''Find which vectors have all-true elements

    Given an array, "a" and indexes into the first elements of vectors
    within that array, return an array where each element is true if
    all elements of the corresponding vector are true.

    Example: a = [ 1,1,0,1,1,1,1], indexes=[0,3]
             vectors = [[1,1,0],[1,1,1,1]]
             return = [False, True]
    '''
    if len(indexes) == 0:
        return np.zeros(0,bool)
    elif len(indexes) == 1:
        return np.all(a)
    cs = np.zeros(len(a)+1,int)
    cs[1:] = np.cumsum(a)
    augmented_indexes = np.zeros(len(indexes)+1, int)
    augmented_indexes[0:-1] = indexes + 1
    augmented_indexes[-1] = len(a) + 1
    counts = augmented_indexes[1:]-augmented_indexes[0:-1]
    hits = cs[augmented_indexes[1:]-1] - cs[augmented_indexes[0:-1]-1]
    return counts == hits

Response:
Find which vectors have all - true elements within that array and indexes into the first elements of the corresponding vectors

   .


Instruction:
Write a Python 3 function that can
calculate measurements of ellipses equivalent to the second moments of labels image labels indexes are the indexes of the objects in the image.

Response:
def ellipse_from_second_moments(image, labels, indexes, wants_compactness = False):
    """Calculate measurements of ellipses equivalent to the second moments of labels

    image  - the intensity at each point
    labels - for each labeled object, derive an ellipse
    indexes - sequence of indexes to process

    returns the following arrays:
       coordinates of the center of the ellipse
       eccentricity
       major axis length
       minor axis length
       orientation
       compactness (if asked for)

    some definitions taken from "Image Moments-Based Structuring and Tracking
    of Objects", LOURENA ROCHA, LUIZ VELHO, PAULO CEZAR P. CARVALHO,
    http://sibgrapi.sid.inpe.br/col/sid.inpe.br/banon/2002/10.23.11.34/doc/35.pdf
    particularly equation 5 (which has some errors in it).
    These yield the rectangle with equivalent second moments. I translate
    to the ellipse by multiplying by 1.154701 which is Matlab's calculation
    of the major and minor axis length for a square of length X divided
    by the actual length of the side of a square of that length.

    eccentricity is the distance between foci divided by the major axis length
    orientation is the angle of the major axis with respect to the X axis
    compactness is the variance of the radial distribution normalized by the area
    """
    if len(indexes) == 0:
        return (np.zeros((0,2)), np.zeros((0,)), np.zeros((0,)),
                np.zeros((0,)),np.zeros((0,)))
    i,j = np.argwhere(labels != 0).transpose()
    return ellipse_from_second_moments_ijv(i,j,image[i,j], labels[i,j], indexes, wants_compactness)


Instruction:
Create a Python 3 function to
calculate measurements of ellipses equivalent to the second moments of labels i,j - coordinates of each point image - the intensity at each point labels - for each labeled object, derive an ellipse indexes - sequence of indexes to process returns the following arrays: coordinates of the center of the ellipse eccentricity major axis length minor axis length orientation some definitions taken from "Image Moments-Based Structuring and Tracking of Objects", LOURENA ROCHA, LUIZ VELHO, PAULO CEZAR P. CARVALHO, http://sibgrapi.sid.inpe.br/col/sid.inpe.br/banon/2002/10.23.11.34/doc/35.pdf particularly equation 5 (which has some errors in it). These yield the rectangle with equivalent second moments. I translate to the ellipse by multiplying by 1.154701 which is Matlab's calculation of the major and minor axis length for a square of length X divided by the actual length of the side of a square of that length. eccentricity is the distance between foci divided by the major axis length orientation is the angle of the major axis with respect to the X axis

Response:
def ellipse_from_second_moments_ijv(i,j, image, labels, indexes, wants_compactness = False):
    """Calculate measurements of ellipses equivalent to the second moments of labels

    i,j - coordinates of each point
    image  - the intensity at each point
    labels - for each labeled object, derive an ellipse
    indexes - sequence of indexes to process

    returns the following arrays:
       coordinates of the center of the ellipse
       eccentricity
       major axis length
       minor axis length
       orientation

    some definitions taken from "Image Moments-Based Structuring and Tracking
    of Objects", LOURENA ROCHA, LUIZ VELHO, PAULO CEZAR P. CARVALHO,
    http://sibgrapi.sid.inpe.br/col/sid.inpe.br/banon/2002/10.23.11.34/doc/35.pdf
    particularly equation 5 (which has some errors in it).
    These yield the rectangle with equivalent second moments. I translate
    to the ellipse by multiplying by 1.154701 which is Matlab's calculation
    of the major and minor axis length for a square of length X divided
    by the actual length of the side of a square of that length.

    eccentricity is the distance between foci divided by the major axis length
    orientation is the angle of the major axis with respect to the X axis
    """
    if len(indexes) == 0:
        return [np.zeros((0,2))] + [np.zeros((0,))] * (5 if wants_compactness else 4)
    if len(i) == 0:
        return ([np.zeros((len(indexes), 2)), np.ones(len(indexes))] +
                [np.zeros(len(indexes))] * (4 if wants_compactness else 3))
    #
    # Normalize to center of object for stability
    #
    nlabels = np.max(indexes)+1
    m = np.array([[None, 0, None],
                  [0, None, None],
                  [None, None, None]], object)
    if np.all(image == 1):
        image = 1
        m[0,0] = intensity = np.bincount(labels)
    else:
        m[0,0] = intensity = np.bincount(labels, image)
    ic = np.bincount(labels, i * image) / intensity
    jc = np.bincount(labels, j * image) / intensity
    i = i - ic[labels]
    j = j - jc[labels]
    #
    # Start by calculating the moments m[p][q] of the image
    # sum(i**p j**q)
    #
    # m[1,0] = 0 via normalization
    # m[0,1] = 0 via normalization
    m[1,1] = np.bincount(labels, i*j*image)
    m[2,0] = np.bincount(labels, i*i*image)
    m[0,2] = np.bincount(labels, j*j*image)

    a = m[2,0] / m[0,0]
    b = 2*m[1,1]/m[0,0]
    c = m[0,2] / m[0,0]

    theta = np.arctan2(b,c-a) / 2
    temp = np.sqrt(b**2+(a-c)**2)
    #
    # If you do a linear regression of the circles from 1 to 50 radius
    # in Matlab, the resultant values fit a line with slope=.9975 and
    # intercept .095. I'm adjusting the lengths accordingly.
    #
    mystery_constant = 0.095
    mystery_multiplier = 0.9975
    major_axis_len = (np.sqrt(8*(a+c+temp)) * mystery_multiplier +
                      mystery_constant)
    minor_axis_len = (np.sqrt(8*(a+c-temp)) * mystery_multiplier +
                      mystery_constant)
    eccentricity = np.sqrt(1-(minor_axis_len / major_axis_len)**2)
    compactness = 2 * np.pi * (a + c) / m[0,0]
    return ([np.column_stack((ic[indexes], jc[indexes])),
             eccentricity[indexes],
             major_axis_len[indexes],
             minor_axis_len[indexes],
             theta[indexes]] +
            ([compactness[indexes]] if wants_compactness else []))


Instruction:
Write a Python 3 script for
calculating the area of each object divided by the area of its bounding box.

Response:
def calculate_extents(labels, indexes):
    """Return the area of each object divided by the area of its bounding box"""
    fix = fixup_scipy_ndimage_result
    areas = fix(scind.sum(np.ones(labels.shape),labels,np.array(indexes, dtype=np.int32)))
    y,x = np.mgrid[0:labels.shape[0],0:labels.shape[1]]
    xmin = fix(scind.minimum(x, labels, indexes))
    xmax = fix(scind.maximum(x, labels, indexes))
    ymin = fix(scind.minimum(y, labels, indexes))
    ymax = fix(scind.maximum(y, labels, indexes))
    bbareas = (xmax-xmin+1)*(ymax-ymin+1)
    return areas / bbareas


Instruction:
Can you create a Python 3 function that
calculates the perimeter vector for the given surrounding points

Response:
def __calculate_perimeter_scoring():
    """Return a 512 element vector which gives the perimeter given surrounding pts

    """
    #
    # This is the array from the paper - a 256 - element array leaving out
    # the center point. The first value is the index, the second, the perimeter
    #
    prashker = np.array([
        [0 ,4    ],[32,4    ],[64,3    ],[96 ,1.414],[128,4    ],[160,4    ],[192,1.414],[224,2.828],
        [1 ,4    ],[33,4    ],[65,3    ],[97 ,1.414],[129,4    ],[161,4    ],[193,3    ],[225,3    ],
        [2 ,3    ],[34,3    ],[66,2    ],[98 ,2    ],[130,3    ],[162,3    ],[194,2    ],[226,2    ],
        [3 ,1.414],[35,1.414],[67,2    ],[99 ,2    ],[131,3    ],[163,3    ],[195,2    ],[227,2    ],
        [4 ,4    ],[36,4    ],[68,3    ],[100,3    ],[132,4    ],[164,4    ],[196,1.414],[228,3    ],
        [5 ,4    ],[37,4    ],[69,3    ],[101,3    ],[133,4    ],[165,4    ],[197,3    ],[229,3    ],
        [6 ,1.414],[38,3    ],[70,2    ],[102,2    ],[134,1.414],[166,3    ],[198,2    ],[230,2    ],
        [7 ,2.828],[39,3    ],[71,2    ],[103,2    ],[135,3    ],[167,3    ],[199,2    ],[231,1.414],
        [8 ,3    ],[40,1.414],[72,2    ],[104,2    ],[136,3    ],[168,1.414],[200,1.414],[232,1.414],
        [9 ,1.414],[41,2.828],[73,1.414],[105,1.414],[137,3    ],[169,3    ],[201,1.414],[233,1.414],
        [10,2    ],[42,1.414],[74,1    ],[106,1    ],[138,2    ],[170,2    ],[202,1    ],[234,1.414],
        [11,2    ],[43,1.414],[75,1    ],[107,1    ],[139,2    ],[171,2    ],[203,1    ],[235,1    ],
        [12,3    ],[44,3    ],[76,2    ],[108,2    ],[140,3    ],[172,3    ],[204,2    ],[236,2    ],
        [13,1.414],[45,3    ],[77,2    ],[109,2    ],[141,3    ],[173,3    ],[205,1.414],[237,1.414],
        [14,1.414],[46,1.414],[78,1    ],[110,1    ],[142,2    ],[174,1.414],[206,2    ],[238,1    ],
        [15,1.414],[47,1.414],[79,1.414],[111,1    ],[143,2    ],[175,1.414],[207,1    ],[239,1    ],
        [16,3    ],[48,3    ],[80,2    ],[112,1.414],[144,1.414],[176,1.414],[208,2    ],[240,1.414],
        [17,3    ],[49,3    ],[81,2    ],[113,2    ],[145,3    ],[177,3    ],[209,2    ],[241,2    ],
        [18,2    ],[50,2    ],[82,1    ],[114,1    ],[146,1.414],[178,2    ],[210,1    ],[242,1.414],
        [19,1.414],[51,2    ],[83,1    ],[115,2    ],[147,1.414],[179,1.414],[211,1    ],[243,1    ],
        [20,1.414],[52,3    ],[84,1.414],[116,1.414],[148,2.828],[180,3    ],[212,1.414],[244,1.414],
        [21,1.414],[53,3    ],[85,2    ],[117,1.414],[149,3    ],[181,3    ],[213,2    ],[245,1.414],
        [22,2    ],[54,2    ],[86,1    ],[118,1    ],[150,1.414],[182,2    ],[214,1    ],[246,1    ],
        [23,1.414],[55,2    ],[87,1.414],[119,1    ],[151,1.414],[183,1.414],[215,1    ],[247,1    ],
        [24,2    ],[56,2    ],[88,1    ],[120,1    ],[152,2    ],[184,2    ],[216,1    ],[248,1    ],
        [25,2    ],[57,2    ],[89,1    ],[121,1.414],[153,2    ],[185,2    ],[217,1    ],[249,1    ],
        [26,1    ],[58,1    ],[90,0    ],[122,0    ],[154,1    ],[186,2    ],[218,0    ],[250,0    ],
        [27,1    ],[59,1.414],[91,0    ],[123,0    ],[155,1    ],[187,1    ],[219,0    ],[251,0    ],
        [28,2    ],[60,2    ],[92,1    ],[124,1    ],[156,2    ],[188,2    ],[220,1.414],[252,1    ],
        [29,2    ],[61,2    ],[93,2    ],[125,1    ],[157,2    ],[189,1.414],[221,1    ],[253,1    ],
        [30,1    ],[62,1    ],[94,0    ],[126,0    ],[158,1.414],[190,1    ],[222,0    ],[254,0    ],
        [31,1    ],[63,1    ],[95,0    ],[127,0    ],[159,1    ],[191,1    ],[223,0    ],[255,0]])
    score = np.zeros((512,))
    i = np.zeros((prashker.shape[0]),int)
    for j in range(4): # 1,2,4,8
        i = i+((prashker[:,0].astype(int) // 2**j)%2)*2**j
    i = i+16
    for j in range(4,8):
        i = i+((prashker[:,0].astype(int) // 2**j)%2)*2**(j+1)
    score[i.astype(int)] = prashker[:,1]
    return score


Instruction:
Can you create a Python 3 function that
calculates the perimeters between adjacent pixels in the perimeters of the labels.

Response:
def calculate_perimeters(labels, indexes):
    """Count the distances between adjacent pixels in the perimeters of the labels"""
    #
    # Create arrays that tell whether a pixel is like its neighbors.
    # index = 0 is the pixel -1,-1 from the pixel of interest, 1 is -1,0, etc.
    #
    m = table_idx_from_labels(labels)
    pixel_score = __perimeter_scoring[m]
    return fixup_scipy_ndimage_result(scind.sum(pixel_score, labels, np.array(indexes,dtype=np.int32)))


Instruction:
Here you have a function in Python 3, explain what it does
def table_idx_from_labels(labels):
    '''Return an array of indexes into a morphology lookup table
    labels - a labels matrix

    returns a matrix of values between 0 and 511 of indices appropriate
    for table_lookup where a pixel's index is determined based on whether
    or not the pixel has the same label as its neighbors (and is labeled)
    '''
    m=np.zeros((labels.shape[0],labels.shape[1]),int)
    exponent = 0
    for i in range(-1,2):
        ilow = (i==-1 and 1) or 0
        iend = (i==1 and labels.shape[0]-1) or labels.shape[0]
        for j in range(-1,2):
            jlow = (j==-1 and 1) or 0
            jend = (j==1 and labels.shape[1]-1) or labels.shape[1]
            #
            # Points outside of bounds are different from what's outside,
            # so set untouched points to "different"
            #
            mask = np.zeros(labels.shape, bool)
            mask[ilow:iend, jlow:jend] = (labels[ilow:iend,jlow:jend] ==
                                          labels[ilow+i:iend+i,jlow+j:jend+j])
            m[mask] += 2**exponent
            exponent += 1
    return m

Response:
Return an array of indexes into a morphology lookup table
    labels - a labels matrix containing the indices of the morphology lookup table containing the pixel s label.


Instruction:
Implement a Python 3 function for
calculating the area of the convex hull of each labeled object.

Response:
def calculate_convex_hull_areas(labels,indexes=None):
    """Calulculate the area of the convex hull of each labeled object

    labels - a label matrix
    indexes - None: calculate convex hull area over entire image
              number: calculate convex hull for a single label
              sequence: calculate convex hull for labels matching a sequence
                        member and return areas in same order.
    """
    if getattr(indexes,"__getitem__",False):
        indexes = np.array(indexes,dtype=np.int32)
    elif indexes is not None:
        indexes = np.array([indexes],dtype=np.int32)
    else:
        labels = labels !=0
        indexes = np.array([1],dtype=np.int32)
    hull, counts = convex_hull(labels, indexes)
    result = np.zeros((counts.shape[0],))
    #
    # Get rid of the degenerate cases
    #
    result[counts==1] = 1 # a single point has area 1
    if not np.any(counts >1):
        return result
    #
    # Given a label number "index_of_label" indexes into the result
    #
    index_of_label = np.zeros((hull[:,0].max()+1),int)
    index_of_label[indexes] = np.array(list(range(indexes.shape[0])))
    #
    # hull_index is the index into hull of the first point on the hull
    # per label
    #
    hull_index = np.zeros((counts.shape[0],),int)
    hull_index[1:] = np.cumsum(counts[:-1])
    #
    # A 2-point case is a line. The area of a line is its length * 1
    # and its length needs to be expanded by 1 because the end-points are
    # at the limits, not the ends.
    #
    if np.any(counts==2):
        diff_2 = hull[hull_index[counts==2],1:]-hull[hull_index[counts==2]+1,1:]
        result[counts==2] = np.sqrt(np.sum(diff_2**2,1))+1
    if not np.any(counts>=3):
        return result
    #
    # Now do the non-degenerate cases (_nd)
    #
    counts_per_label = np.zeros((hull[:,0].max()+1),counts.dtype)
    counts_per_label[indexes] = counts
    hull_nd = hull[counts_per_label[hull[:,0]] >=3]
    counts_nd = counts[counts>=3]
    indexes_nd = indexes[counts>=3]
    index_of_label_nd = np.zeros((index_of_label.shape[0],),int)
    index_of_label_nd[indexes_nd] = np.array(list(range(indexes_nd.shape[0])))
    #
    # Figure out the within-label index of each point in a label. This is
    # so we can do modulo arithmetic when pairing a point with the next
    # when determining an edge
    #
    hull_index_nd = np.zeros((counts_nd.shape[0],),int)
    if hull_index_nd.shape[0] > 1:
        hull_index_nd[1:] = np.cumsum(counts_nd[:-1])
    index_of_label_per_pixel_nd = index_of_label_nd[hull_nd[:,0]]
    hull_index_per_pixel_nd = hull_index_nd[index_of_label_per_pixel_nd]
    within_label_index = (np.array(list(range(hull_nd.shape[0]))) -
                          hull_index_per_pixel_nd)
    #
    # Find some point within each convex hull.
    #
    within_hull = np.zeros((counts_nd.shape[0],2))
    within_hull[:,0] = scind.sum(hull_nd[:,1],
                                 hull_nd[:,0],
                                 indexes_nd) / counts_nd
    within_hull[:,1] = scind.sum(hull_nd[:,2],
                                 hull_nd[:,0],
                                 indexes_nd) / counts_nd
    within_hull_per_pixel = within_hull[index_of_label_per_pixel_nd]
    #
    # Now, we do a little, slightly wierd fixup, arguing that the
    # edge of a pixel is +/- .5 of its coordinate. So we move the ones
    # left of center to the left by .5, right of center to the right by .5
    # etc.
    #
    # It works for a square...
    #
    # 2012-06-04 (thouis): Numpy trunk no longer allows unsafe type conversion
    # in in-place operations, which was what used to happen here
    # (adding/subtracting .5 from an integer array, which then just got
    # truncated).  So now it just adds 1 to the pixels right or below the
    # center.
    #
    # It would probably be better to do this in one of two other ways:
    # - Compute area with a circular ferret
    #   = polygon area + radius * perimeter + pi * radius^2
    # - Compute area with a square ferret
    #   = polygon area + 2 * radius * (horizontal_projection + vertical_projection) + 4*radius^2
    hull_nd[hull_nd[:,1] >= within_hull_per_pixel[:,0],1] += 1
    hull_nd[hull_nd[:,2] >= within_hull_per_pixel[:,1],2] += 1
    #
    # Finally, we go around the circle, computing triangle areas
    # from point n to point n+1 (modulo count) to the point within
    # the hull.
    #
    plus_one_idx = np.array(list(range(hull_nd.shape[0])))+1
    modulo_mask = within_label_index+1 == counts_nd[index_of_label_per_pixel_nd]
    plus_one_idx[modulo_mask] = hull_index_per_pixel_nd[modulo_mask]
    area_per_pt_nd = triangle_areas(hull_nd[:,1:],
                                    hull_nd[plus_one_idx,1:],
                                    within_hull_per_pixel)
    #
    # The convex area is the sum of these triangles
    #
    result[counts>=3] = scind.sum(area_per_pt_nd, hull_nd[:,0], indexes_nd)
    return result


Instruction:
Can you write a function in Python 3 where it
calculates the solidity of each label in the cluster.

Response:
def calculate_solidity(labels,indexes=None):
    """Calculate the area of each label divided by the area of its convex hull

    labels - a label matrix
    indexes - the indexes of the labels to measure
    """
    if indexes is not None:
        """ Convert to compat 32bit integer """
        indexes = np.array(indexes,dtype=np.int32)
    areas = scind.sum(np.ones(labels.shape),labels,indexes)
    convex_hull_areas = calculate_convex_hull_areas(labels, indexes)
    return areas / convex_hull_areas


Instruction:
Explain what the following Python 3 code does
def euler_number(labels, indexes=None):
    if indexes is None:
        labels = labels != 0
        indexes = np.array([1],dtype=np.int32)
    elif getattr(indexes,'__getitem__',False):
        indexes = np.array(indexes,dtype=np.int32)
    else:
        indexes = np.array([indexes],dtype=np.int32)
    fix = fixup_scipy_ndimage_result
    #
    # The algorithm here is from the following reference:
    # S.B. Gray, "Local Properties of Binary Images in Two Dimensions",
    # IEEE Transactions on Computers, Vol c-20 # 5 p 551, May 1971
    #
    # The general idea is that crossings into objects can be measured locally
    # through counting numbers of patterns resulting in crossings. There
    # are three sets that are applicable in Euler Numbers:
    # Q1: 1 0  0 1  0 0  0 0 (or more simply, 1 bit per quad)
    #     0 0  0 0  1 0  0 1
    #
    # Q3: 0 1  1 0  1 1  1 1 (or 3 bits per quad)
    #     1 1  1 1  1 0  0 1
    #
    # QD: 1 0  0 1
    #     0 1  1 0
    #
    # and the Euler number = W of an object is
    #
    # 4W = n(Q1) - n(Q3) - 2n(QD) (equation 34)
    # W  = (n(Q1) - n(Q3) - 2n(QD))/4
    #
    # We shift the label matrix to make matrices, padded by zeros on the
    # sides for each of the four positions of the quad:
    # I00 I01
    # I10 I11
    #
    # We can then assign each bitquad to a label based on the value
    # of the label at one of the "on" bits. For example, the first pattern
    # of Q1 has the label I00 because that bit is on. It's truth value is
    # I00 != I01 and I00 != I02 and I00 != I03.
    #
    I_shape = (labels.shape[0]+3,labels.shape[1]+3)
    I00 = np.zeros(I_shape,int)
    I01 = np.zeros(I_shape,int)
    I10 = np.zeros(I_shape,int)
    I11 = np.zeros(I_shape,int)
    slice_00 = [slice(1,labels.shape[0]+1),slice(1,labels.shape[1]+1)]
    slice_01 = [slice(1,labels.shape[0]+1),slice(0,labels.shape[1])]
    slice_10 = [slice(labels.shape[0]),slice(1,labels.shape[1]+1)]
    slice_11 = [slice(0,labels.shape[0]),slice(0,labels.shape[1])]
    I00[slice_00] = labels
    I01[slice_01] = labels
    I10[slice_10] = labels
    I11[slice_11] = labels
    #
    # There are 6 binary comparisons among the four bits
    #
    EQ00_01 = I00 == I01;              EQ01_00 = EQ00_01
    EQ00_10 = I00 == I10;              EQ10_00 = EQ00_10
    EQ00_11 = I00 == I11;              EQ11_00 = EQ00_11
    EQ01_10 = I01 == I10;              EQ10_01 = EQ01_10
    EQ01_11 = I01 == I11;              EQ11_01 = EQ01_11
    EQ10_11 = I10 == I11;              EQ11_10 = EQ10_11
    NE00_01 = np.logical_not(EQ00_01); NE01_00 = NE00_01
    NE00_10 = np.logical_not(EQ00_10); NE10_00 = NE00_10
    NE00_11 = np.logical_not(EQ00_11); NE11_00 = NE00_11
    NE01_10 = np.logical_not(EQ01_10); NE10_01 = NE01_10
    NE01_11 = np.logical_not(EQ01_11); NE11_01 = NE01_11
    NE10_11 = np.logical_not(EQ10_11); NE11_10 = NE10_11
    #
    # Q1: 1 0
    #     0 0
    Q1_condition = (NE00_01 & NE00_10 & NE00_11).astype(int)
    #     0 1
    #     0 0
    Q1_condition[slice_00] += (NE01_00 & NE01_10 & NE01_11)[slice_01]
    #     0 0
    #     1 0
    Q1_condition[slice_00] += (NE10_00 & NE10_01 & NE10_11)[slice_10]
    #     0 0
    #     0 1
    Q1_condition[slice_00] += (NE11_00 & NE11_01 & NE11_10)[slice_11]
    Q1 = fix(scind.sum(Q1_condition, I00, indexes))
    #
    # Q3: 1 1
    #     1 0
    Q3_condition = (EQ00_10 & EQ00_01 & NE00_11).astype(int)
    #     0 1
    #     1 1
    Q3_condition[slice_00] += (NE11_00 & EQ11_10 & EQ11_01)[slice_11]
    #     1 0
    #     1 1
    Q3_condition += (NE00_01 & EQ00_10 & EQ00_11)
    #     1 1
    #     0 1
    Q3_condition += (NE00_10 & EQ00_01 & EQ00_11)
    Q3 = fix(scind.sum(Q3_condition, I00, indexes))
    # QD: 1 0
    #     0 1
    QD_condition = (NE00_01 & NE00_10 & EQ00_11).astype(int)
    #     0 1
    #     1 0
    QD_condition[slice_00] += (NE01_00 & NE01_11 & EQ01_10)[slice_01]
    QD  = fix(scind.sum(QD_condition, I00, indexes))
    W = (Q1 - Q3 - 2*QD).astype(float)/4.0
    if indexes is None:
        return W[0]
    return W

Response: Calculate the Euler number of each label in the binary image.


Instruction:
Can you tell what is the following Python 3 function doing
def block(shape, block_shape):
    shape = np.array(shape)
    block_shape = np.array(block_shape)
    i,j = np.mgrid[0:shape[0],0:shape[1]]
    ijmax = (shape.astype(float)/block_shape.astype(float)).astype(int)
    ijmax = np.maximum(ijmax, 1)
    multiplier = ijmax.astype(float) / shape.astype(float)
    i = (i * multiplier[0]).astype(int)
    j = (j * multiplier[1]).astype(int)
    labels = i * ijmax[1] + j
    indexes = np.array(list(range(np.product(ijmax))))
    return labels, indexes

Response: Create a labels image that divides the image into blocks and returns the indexes of all labels generated by block - process the image


Instruction:
Can you generate the documentation for the following Python 3 function
def white_tophat(image, radius=None, mask=None, footprint=None):
    '''White tophat filter an image using a circular structuring element

    image - image in question
    radius - radius of the circular structuring element. If no radius, use
             an 8-connected structuring element.
    mask  - mask of significant pixels in the image. Points outside of
            the mask will not participate in the morphological operations
    '''
    #
    # Subtract the opening to get the tophat
    #
    final_image = image - opening(image, radius, mask, footprint)
    #
    # Paint the masked pixels into the final image
    #
    if not mask is None:
        not_mask = np.logical_not(mask)
        final_image[not_mask] = image[not_mask]
    return final_image

Response: White tophat filter an image using a circular structuring element.


Instruction:
Write a Python 3 function that can
black tophat filter an image using a circular structuring element.

Response:
def black_tophat(image, radius=None, mask=None, footprint=None):
    '''Black tophat filter an image using a circular structuring element

    image - image in question
    radius - radius of the circular structuring element. If no radius, use
             an 8-connected structuring element.
    mask  - mask of significant pixels in the image. Points outside of
            the mask will not participate in the morphological operations
    '''
    #
    # Subtract the image from the closing to get the bothat
    #
    final_image = closing(image, radius, mask, footprint) - image
    #
    # Paint the masked pixels into the final image
    #
    if not mask is None:
        not_mask = np.logical_not(mask)
        final_image[not_mask] = image[not_mask]
    return final_image


Instruction:
Given the following Python 3 function, write the documentation
def grey_erosion(image, radius=None, mask=None, footprint=None):
    '''Perform a grey erosion with masking'''
    if footprint is None:
        if radius is None:
            footprint = np.ones((3,3),bool)
            radius = 1
        else:
            footprint = strel_disk(radius)==1
    else:
        radius = max(1, np.max(np.array(footprint.shape) // 2))
    iradius = int(np.ceil(radius))
    #
    # Do a grey_erosion with masked pixels = 1 so they don't participate
    #
    big_image = np.ones(np.array(image.shape)+iradius*2)
    big_image[iradius:-iradius,iradius:-iradius] = image
    if not mask is None:
        not_mask = np.logical_not(mask)
        big_image[iradius:-iradius,iradius:-iradius][not_mask] = 1
    processed_image = scind.grey_erosion(big_image, footprint=footprint)
    final_image = processed_image[iradius:-iradius,iradius:-iradius]
    if not mask is None:
        final_image[not_mask] = image[not_mask]
    return final_image

Response: Perform a grey erosion with masking


Instruction:
Make a summary of the following Python 3 code
def grey_reconstruction(image, mask, footprint=None, offset=None):
    '''Perform a morphological reconstruction of the image

    grey_dilate the image, constraining each pixel to have a value that is
    at most that of the mask.
    image - the seed image
    mask - the mask, giving the maximum allowed value at each point
    footprint - a boolean array giving the neighborhood pixels to be used
                in the dilation. None = 8-connected

    The algorithm is taken from:
    Robinson, "Efficient morphological reconstruction: a downhill filter",
    Pattern Recognition Letters 25 (2004) 1759-1767
    '''
    assert tuple(image.shape) == tuple(mask.shape)
    assert np.all(image <= mask)
    if footprint is None:
        footprint = np.ones([3]*image.ndim, bool)
    else:
        footprint = footprint.copy()

    if offset is None:
        assert all([d % 2 == 1 for d in footprint.shape]),\
               "Footprint dimensions must all be odd"
        offset = np.array([d//2 for d in footprint.shape])
    # Cross out the center of the footprint
    footprint[[slice(d,d+1) for d in offset]] = False
    #
    # Construct an array that's padded on the edges so we can ignore boundaries
    # The array is a dstack of the image and the mask; this lets us interleave
    # image and mask pixels when sorting which makes list manipulations easier
    #
    padding = (np.array(footprint.shape)//2).astype(int)
    dims = np.zeros(image.ndim+1,int)
    dims[1:] = np.array(image.shape)+2*padding
    dims[0] = 2
    inside_slices = [slice(p,-p) for p in padding]
    values = np.ones(dims)*np.min(image)
    values[[0]+inside_slices] = image
    values[[1]+inside_slices] = mask
    #
    # Create a list of strides across the array to get the neighbors
    # within a flattened array
    #
    value_stride = np.array(values.strides[1:]) // values.dtype.itemsize
    image_stride = values.strides[0] // values.dtype.itemsize
    footprint_mgrid = np.mgrid[[slice(-o,d - o)
                                for d,o in zip(footprint.shape,offset)]]
    footprint_offsets = footprint_mgrid[:,footprint].transpose()
    strides = np.array([np.sum(value_stride * footprint_offset)
                        for footprint_offset in footprint_offsets],
                       np.int32)
    values = values.flatten()
    value_sort = np.lexsort([-values]).astype(np.int32)
    #
    # Make a linked list of pixels sorted by value. -1 is the list terminator.
    #
    prev = -np.ones(len(values), np.int32)
    next = -np.ones(len(values), np.int32)
    prev[value_sort[1:]] = value_sort[:-1]
    next[value_sort[:-1]] = value_sort[1:]
    #
    # Create a rank-order value array so that the Cython inner-loop
    # can operate on a uniform data type
    #
    values, value_map = rank_order(values)
    current = value_sort[0]
    slow = False

    if slow:
        while current != -1:
            if current < image_stride:
                current_value = values[current]
                if current_value == 0:
                    break
                neighbors = strides+current
                for neighbor in neighbors:
                    if neighbor < 0:
                        raise IndexError("Index out of bounds: %d, current=%d, current_value=%d"%
                                         (neighbor,
                                          np.unravel_index(current, dims),
                                          current_value))
                    neighbor_value = values[neighbor]
                    # Only do neighbors less than the current value
                    if neighbor_value < current_value:
                        mask_value = values[neighbor + image_stride]
                        # Only do neighbors less than the mask value
                        if neighbor_value < mask_value:
                            # Raise the neighbor to the mask value if
                            # the mask is less than current
                            if mask_value < current_value:
                                link = neighbor + image_stride
                                values[neighbor] = mask_value
                            else:
                                link = current
                                values[neighbor] = current_value
                            # unlink the neighbor
                            nprev = prev[neighbor]
                            nnext = next[neighbor]
                            next[nprev] = nnext
                            if nnext != -1:
                                prev[nnext] = nprev
                            # link the neighbor after the link
                            next[neighbor] = next[link]
                            prev[neighbor] = link
                            prev[next[link]] = neighbor
                            next[link] = neighbor
            current = next[current]
    else:
        grey_reconstruction_loop(values, prev, next, strides, current,
                                 image_stride)
    #
    # Reshape the values array to the shape of the padded image
    # and return the unpadded portion of that result
    #
    values = value_map[values[:image_stride]]
    values.shape = np.array(image.shape)+2*padding
    return values[inside_slices]

Response: Perform a morphological reconstruction of the image using the greyscale algorithm.


Instruction:
Implement a function in Python 3 to
do a morphological opening of a structureuring element.

Response:
def opening(image, radius=None, mask=None, footprint=None):
    '''Do a morphological opening

    image - pixel image to operate on
    radius - use a structuring element with the given radius. If no radius,
             use an 8-connected structuring element.
    mask - if present, only use unmasked pixels for operations
    '''
    eroded_image = grey_erosion(image, radius, mask, footprint)
    return grey_dilation(eroded_image, radius, mask, footprint)


Instruction:
Can you generate a brief explanation for the following Python 3 code
def closing(image, radius=None, mask=None, footprint = None):
    '''Do a morphological closing

    image - pixel image to operate on
    radius - use a structuring element with the given radius. If no structuring
             element, use an 8-connected structuring element.
    mask - if present, only use unmasked pixels for operations
    '''
    dilated_image = grey_dilation(image, radius, mask, footprint)
    return grey_erosion(dilated_image, radius, mask, footprint)

Response: Do a morphological closing


Instruction:
Create a Python 3 function for
performing morphological opening along lines of different angles.

Response:
def openlines(image, linelength=10, dAngle=10, mask=None):
    """
    Do a morphological opening along lines of different angles.

    Return difference between max and min response to different angles for each pixel.
    This effectively removes dots and only keeps lines.

    image - pixel image to operate on
    length - length of the structural element
    angluar_resolution - angle step for the rotating lines
    mask - if present, only use unmasked pixels for operations
    """
    nAngles = 180//dAngle
    openingstack = np.zeros((nAngles,image.shape[0],image.shape[1]),image.dtype)

    for iAngle in range(nAngles):
        angle = dAngle * iAngle
        se = strel_line(linelength,angle)
        openingstack[iAngle,:,:] = opening(image, mask=mask, footprint=se)

    imLines = np.max(openingstack,axis=0) - np.min(openingstack,axis=0)
    return imLines


Instruction:
Can you generate a brief explanation for the following Python 3 code
def table_lookup(image, table, border_value, iterations = None):
    '''Perform a morphological transform on an image, directed by its neighbors

    image - a binary image
    table - a 512-element table giving the transform of each pixel given
            the values of that pixel and its 8-connected neighbors.
    border_value - the value of pixels beyond the border of the image.
                   This should test as True or False.

    The pixels are numbered like this:

    0 1 2
    3 4 5
    6 7 8
    The index at a pixel is the sum of 2**<pixel-number> for pixels
    that evaluate to true.
    '''
    #
    # Test for a table that never transforms a zero into a one:
    #
    center_is_zero = np.array([(x & 2**4) == 0 for x in range(2**9)])
    use_index_trick = False
    if (not np.any(table[center_is_zero]) and
        (np.issubdtype(image.dtype, bool) or np.issubdtype(image.dtype, int))):
        # Use the index trick
        use_index_trick = True
        invert = False
    elif (np.all(table[~center_is_zero]) and np.issubdtype(image.dtype, bool)):
        # All ones stay ones, invert the table and the image and do the trick
        use_index_trick = True
        invert = True
        image = ~ image
        # table index 0 -> 511 and the output is reversed
        table = ~ table[511-np.arange(512)]
        border_value = not border_value
    if use_index_trick:
        orig_image = image
        index_i, index_j, image = prepare_for_index_lookup(image, border_value)
        index_i, index_j = index_lookup(index_i, index_j,
                                        image, table, iterations)
        image = extract_from_image_lookup(orig_image, index_i, index_j)
        if invert:
            image = ~ image
        return image

    counter = 0
    while counter != iterations:
        counter += 1
        #
        # We accumulate into the indexer to get the index into the table
        # at each point in the image
        #
        if image.shape[0] < 3 or image.shape[1] < 3:
            image = image.astype(bool)
            indexer = np.zeros(image.shape,int)
            indexer[1:,1:]   += image[:-1,:-1] * 2**0
            indexer[1:,:]    += image[:-1,:] * 2**1
            indexer[1:,:-1]  += image[:-1,1:] * 2**2

            indexer[:,1:]    += image[:,:-1] * 2**3
            indexer[:,:]     += image[:,:] * 2**4
            indexer[:,:-1]   += image[:,1:] * 2**5

            indexer[:-1,1:]  += image[1:,:-1] * 2**6
            indexer[:-1,:]   += image[1:,:] * 2**7
            indexer[:-1,:-1] += image[1:,1:] * 2**8
        else:
            indexer = table_lookup_index(np.ascontiguousarray(image,np.uint8))
        if border_value:
            indexer[0,:]   |= 2**0 + 2**1 + 2**2
            indexer[-1,:]  |= 2**6 + 2**7 + 2**8
            indexer[:,0]   |= 2**0 + 2**3 + 2**6
            indexer[:,-1]  |= 2**2 + 2**5 + 2**8
        new_image = table[indexer]
        if np.all(new_image == image):
            break
        image = new_image
    return image

Response: Perform a morphological transform on an image and a 512 - element table giving the transform of each pixel and its 8 - connected neighbors.


Instruction:
Explain what the following Python 3 code does
def pattern_of(index):
    '''Return the pattern represented by an index value'''
    return np.array([[index & 2**0,index & 2**1,index & 2**2],
                     [index & 2**3,index & 2**4,index & 2**5],
                     [index & 2**6,index & 2**7,index & 2**8]], bool)

Response: Return the pattern represented by an index value


Instruction:
Can you generate the documentation for the following Python 3 function
def index_of(pattern):
    '''Return the index of a given pattern'''
    return (pattern[0,0] * 2**0 + pattern[0,1] * 2**1 + pattern[0,2] * 2**2 +
            pattern[1,0] * 2**3 + pattern[1,1] * 2**4 + pattern[1,2] * 2**5 +
            pattern[2,0] * 2**6 + pattern[2,1] * 2**7 + pattern[2,2] * 2**8)

Response: Return the index of a given pattern


Instruction:
Write a Python 3 script to
remove all pixels from an image except for branchpoints

Response:
def branchpoints(image, mask=None):
    '''Remove all pixels from an image except for branchpoints

    image - a skeletonized image
    mask -  a mask of pixels excluded from consideration

    1 0 1    ? 0 ?
    0 1 0 -> 0 1 0
    0 1 0    0 ? 0
    '''
    global branchpoints_table
    if mask is None:
        masked_image = image
    else:
        masked_image = image.astype(bool).copy()
        masked_image[~mask] = False
    result = table_lookup(masked_image, branchpoints_table, False, 1)
    if not mask is None:
        result[~mask] = image[~mask]
    return result


Instruction:
Can you tell what is the following Python 3 function doing
def branchings(image, mask=None):
    '''Count the number of branches eminating from each pixel

    image - a binary image
    mask - optional mask of pixels not to consider

    This is the count of the number of branches that
    eminate from a pixel. A pixel with neighbors fore
    and aft has branches fore and aft = 2. An endpoint
    has one branch. A fork has 3. Finally, there's
    the quadrabranch which has 4:
    1 0 1
    0 1 0 -> 4
    1 0 1
    '''
    global branchings_table
    if mask is None:
        masked_image = image
    else:
        masked_image = image.astype(bool).copy()
        masked_image[~mask] = False
    #
    # Not a binary operation, so we do a convolution with the following
    # kernel to get the indices into the table.
    #
    kernel = np.array([[1,2,4],
                       [8,16,32],
                       [64,128,256]])
    indexer = scind.convolve(masked_image.astype(int), kernel,
                             mode='constant').astype(int)
    result = branchings_table[indexer]
    return result

Response: Count the number of branches eminating from each pixel in the image.


Instruction:
How would you explain what the following Python 3 function does
def bridge(image, mask=None, iterations = 1):
    '''Fill in pixels that bridge gaps.

    1 0 0    1 0 0
    0 0 0 -> 0 1 0
    0 0 1    0 0 1
    '''
    global bridge_table
    if mask is None:
        masked_image = image
    else:
        masked_image = image.astype(bool).copy()
        masked_image[~mask] = False
    result = table_lookup(masked_image, bridge_table, False, iterations)
    if not mask is None:
        result[~mask] = image[~mask]
    return result

Response: Fill in pixels that bridge gaps.


Instruction:
How would you explain what the following Python 3 function does
def clean(image, mask=None, iterations = 1):
    '''Remove isolated pixels

    0 0 0     0 0 0
    0 1 0 ->  0 0 0
    0 0 0     0 0 0

    Border pixels and pixels adjoining masks are removed unless one valid
    neighbor is true.
    '''
    global clean_table
    if mask is None:
        masked_image = image
    else:
        masked_image = image.astype(bool).copy()
        masked_image[~mask] = False
    result = table_lookup(masked_image, clean_table, False, iterations)
    if not mask is None:
        result[~mask] = image[~mask]
    return result

Response: Remove isolated pixels from an image and remove any pixels adjoining masks.


Instruction:
Can you tell what is the following Python 3 function doing
def diag(image, mask=None, iterations=1):
    '''4-connect pixels that are 8-connected

    0 0 0     0 0 ?
    0 0 1 ->  0 1 1
    0 1 0     ? 1 ?

    '''
    global diag_table
    if mask is None:
        masked_image = image
    else:
        masked_image = image.astype(bool).copy()
        masked_image[~mask] = False
    result = table_lookup(masked_image, diag_table, False, iterations)
    if not mask is None:
        result[~mask] = image[~mask]
    return result

Response:
4-connect pixels that are 8-connected

    0 0 0     0 0 ?
    0 0 1 ->  0 1 1
    0 1 0     ? 1 ?


Instruction:
Can you generate a brief explanation for the following Python 3 code
def endpoints(image, mask=None):
    '''Remove all pixels from an image except for endpoints

    image - a skeletonized image
    mask -  a mask of pixels excluded from consideration

    1 0 0    ? 0 0
    0 1 0 -> 0 1 0
    0 0 0    0 0 0
    '''
    global endpoints_table
    if mask is None:
        masked_image = image
    else:
        masked_image = image.astype(bool).copy()
        masked_image[~mask] = False
    result = table_lookup(masked_image, endpoints_table, False, 1)
    if not mask is None:
        result[~mask] = image[~mask]
    return result

Response: Remove all pixels from an image except for endpoints


Instruction:
Create a Python 3 function to
fill isolated black pixels with the specified mask.

Response:
def fill(image, mask=None, iterations=1):
    '''Fill isolated black pixels

    1 1 1     1 1 1
    1 0 1 ->  1 1 1
    1 1 1     1 1 1
    '''
    global fill_table
    if mask is None:
        masked_image = image
    else:
        masked_image = image.astype(bool).copy()
        masked_image[~mask] = True
    result = table_lookup(masked_image, fill_table, True, iterations)
    if not mask is None:
        result[~mask] = image[~mask]
    return result


Instruction:
Create a Python 3 function to
fill 4 - connected black pixels with mask

Response:
def fill4(image, mask=None, iterations=1):
    '''Fill 4-connected black pixels

    x 1 x     x 1 x
    1 0 1 ->  1 1 1
    x 1 x     x 1 x
    '''
    global fill4_table
    if mask is None:
        masked_image = image
    else:
        masked_image = image.astype(bool).copy()
        masked_image[~mask] = True
    result = table_lookup(masked_image, fill4_table, True, iterations)
    if not mask is None:
        result[~mask] = image[~mask]
    return result