Search code examples
pythonmathimage-processingcomputer-vision

How to unroll the drawing of a spiral?


A spiral is a flat curve that rotates around a central axis.

spiral

I have a drawing of a parkinsonian patient that is based on a spiral.

drawing

As you can see, this image of the patient's drawing oscillates around the base spiral. What I would like to do is the following: "unroll" the spiral so that both the oscillation of the drawing and the spiral itself is based on a straight line, that is, to linearize the spiral. How can I do this?


Solution

  • Here is a possible approach in two parts.

    The first part tries to align a spiral with the image. The simplest spiral is an Archimedian spiral where the radius and the angle are linearly coupled. By plotting and looking at the coordinates, the limits for an approximate scale for the x and the y directions are found. The result isn't perfect. Maybe the given image isn't nicely scanned, but just a photo giving rise to deformations or the original spiral wasn't a perfect Archimedian spiral. (Also, a png file would be strongly preferred instead of the given jpg). Anyway, the scale is good enough to give an idea how the algorithm would work, preferably starting from an exact scan.

    The next part goes through each pixel of the image and finds it corresponding angle and distance to the center (using the scaling found in the first part). The next step is to find how many times the angle has gone around (in multiples of 2 pi), choosing the closest match. Subtracting the radius from the angle would straighten the spiral.

    Some code to illustrate the idea.

    import numpy as np
    from matplotlib import pyplot as plt
    import imageio
    
    fig, ax = plt.subplots()
    
    img = imageio.imread('spiral_follow.jpg')
    # the image extent is set using trial and error to align with the spiral equation;
    # the center of the spiral should end up with coordinates 0,0
    x0, x1, y0, y1 = extent = [-17.8, 16, 13, -16.8]
    ax.imshow(img, extent=extent)
    
    # t=17.4 is about where the spiral ends; the exact value is not important
    t = np.linspace(0, 17.4, 1000) 
    r = t
    theta = t
    sx = r * np.sin(theta)
    sy = r * np.cos(theta)
    
    ax.plot(sx, sy, c='lime', lw=6, alpha=0.4) # plot the spiral over the image
    
    # r_p and theta_p are the polar coordinates of the white pixels
    r_p = []
    theta_p = []
    for i in range(img.shape[0]):
        for j in range(img.shape[1]):
            if img[i,j] > 127: # the black pixels are 0, the white are 255; 127 is the middle
                # transform from pixel coordinates (i,j) to the coordinatets of the spiral (x,y)
                x = x0 + j * (x1 - x0) / (img.shape[1] - 1)
                y = y1 + i * (y0 - y1) / (img.shape[0] - 1)
                # transform from carthesian (x,y) to polar coordinates (theta,r)
                theta = np.arctan2(x, y)
                r = np.sqrt(x*x+y*y)
                # the following code tries to find out how many times 2pi should be added to theta
                #   in order to correspond best to r
                k = np.round((r - theta) / (2 * np.pi))
                nearest_theta = theta + 2 * k * np.pi
                theta_p.append(nearest_theta)
                r_p.append(nearest_theta - r)
    plt.show()
    
    fig, ax = plt.subplots()
    ax.set_facecolor('black')
    ax.scatter(theta_p, r_p, s=1, lw=0, color='white')
    plt.show()
    

    The aligned spiral:

    aligned spiral

    The straightened spiral:

    straightened spiral