def all_connected_components(i,j):
    '''Associate each label in i with a component #
    
    This function finds all connected components given an array of
    associations between labels i and j using a depth-first search.
    
    i & j give the edges of the graph. The first step of the algorithm makes
    bidirectional edges, (i->j and j<-i), so it's best to only send the
    edges in one direction (although the algorithm can withstand duplicates).
    
    returns a label for each vertex up to the maximum named vertex in i.
    '''
    if len(i) == 0:
        return i
    i1 = np.hstack((i,j))
    j1 = np.hstack((j,i))
    order = np.lexsort((j1,i1))
    i=np.ascontiguousarray(i1[order],np.uint32)
    j=np.ascontiguousarray(j1[order],np.uint32)
    #
    # Get indexes and counts of edges per vertex
    #
    counts = np.ascontiguousarray(np.bincount(i.astype(int)),np.uint32)
    indexes = np.ascontiguousarray(np.cumsum(counts)-counts,np.uint32)
    #
    # This stores the lowest index # during the algorithm - the first
    # vertex to be labeled in a connected component.
    #
    labels = np.zeros(len(counts), np.uint32)
    _all_connected_components(i,j,indexes,counts,labels)
    return labels