Scan pixels in a ring pattern around a center point clockwise

    :param x0: Center x-coordinate
    :type x0: int
    :param y0: Center y-coordinate
    :type y0: int
    :param r1: Initial radius
    :type r1: int
    :param r2: Final radius
    :type r2: int
    :param metric: Distance metric
    :type metric: function
    :returns: Coordinate generator
    :rtype: function

def ringscan(x0, y0, r1, r2, metric=chebyshev):
    """Scan pixels in a ring pattern around a center point clockwise

    :param x0: Center x-coordinate
    :type x0: int
    :param y0: Center y-coordinate
    :type y0: int
    :param r1: Initial radius
    :type r1: int
    :param r2: Final radius
    :type r2: int
    :param metric: Distance metric
    :type metric: function
    :returns: Coordinate generator
    :rtype: function
    """

    # Validate inputs
    if r1 < 0: raise ValueError("Initial radius must be non-negative")
    if r2 < 0: raise ValueError("Final radius must be non-negative")
    if not hasattr(metric, "__call__"): raise TypeError("Metric not callable")

    # Define clockwise step directions
    direction = 0
    steps = {0: [ 1, 0],
             1: [ 1,-1],
             2: [ 0,-1],
             3: [-1,-1],
             4: [-1, 0],
             5: [-1, 1],
             6: [ 0, 1],
             7: [ 1, 1]}
    nsteps = len(steps)

    center = [x0, y0]
    
    # Scan distances outward (1) or inward (-1)
    rstep = 1 if r2 >= r1 else -1
    for distance in range(r1, r2 + rstep, rstep):

        initial = [x0, y0 + distance]
        current = initial

        # Number of tries to find a valid neighrbor
        ntrys = 0

        while True:

            # Short-circuit special case
            if distance == 0:
                yield current[0], current[1]
                break

            # Try and take a step and check if still within distance
            nextpoint = [current[i] + steps[direction][i] for i in range(2)]
            if metric(center, nextpoint) != distance:

                # Check if we tried all step directions and failed
                ntrys += 1
                if ntrys == nsteps:
                    break
                
                # Try the next direction
                direction = (direction + 1) % nsteps
                continue

            ntrys = 0
            yield current[0], current[1]

            # Check if we have come all the way around
            current = nextpoint
            if current == initial:
                break

        # Check if we tried all step directions and failed
        if ntrys == nsteps:
            break