Forum Archive

Turn image into spiral art

Jason

Hi all. I’m sharing a little script I crafted that turns an input image into gray scale, and then makes a spiral to approximate it.

When run, the script prompts you to choose an image to process or use the Lena demo.

''' convert image to a big spiral
'''

dr1=3.0  # delta radius for inner spiral
dr2=3    # delta radius for outter spiral
lseg=4   # length of spiral segment treated as a line
nlayers=4 # NO. of layers to segment gray scale img
sigma=3  # radius for Gaussian blur



#--------Import modules-------------------------
import numpy as np
from PIL import Image
from PIL import ImageFilter
import matplotlib.pyplot as plt
import dialogs
import photos

#-------------Main---------------------------------
if __name__=='__main__':

    #----------Read image-----
    i = dialogs.alert('Image', '', 'Demo Image', 'Select from Photos')
    if i == 1:
        img = Image.open('test:Lenna')
    else:
        img = photos.pick_image()

    img=img.convert('L')

    # resize
    newsize=(300, int(float(img.size[1])/img.size[0]*300))
    img=img.resize(newsize,Image.ANTIALIAS)
    print('Image size: %s' %str(img.size))

    # blur
    img=img.filter(ImageFilter.GaussianBlur(sigma))

    # convert to array
    img=np.array(img)
    img=img[::-1,:]

    # thresholding
    layers=np.linspace(np.min(img),np.max(img),nlayers+1)
    img_layers=np.zeros(img.shape)
    ii=1
    for z1,z2 in zip(layers[:-1],layers[1:]):
        img_layers=np.where((img>=z1) & (img<z2),ii,img_layers)
        ii+=1
    img_layers=img_layers.max()-img_layers+1

    # get diagonal length
    size=img.shape # ny,nx
    diag=np.sqrt(size[0]**2/4+size[1]**2/4)

    figure=plt.figure(figsize=(12,10),dpi=100)
    ax=figure.add_subplot(111)

    # create spiral
    rii=1.0
    line=np.zeros(img.shape)
    nc=0
    while True:
        if nc==0:
            rii2=rii+dr1
        elif nc<0:
            rii=rii2
            rii2=rii+dr1
        else:
            rii=rii2
            rii2=rii+dr2

        print('r = %.1f' %rii)
        nii=max(64,2*np.pi*rii//lseg)
        tii=np.linspace(0,2*np.pi,nii)
        riis=np.linspace(rii,rii2,nii)
        xii=riis*np.cos(tii)
        yii=riis*np.sin(tii)

        if np.all(riis>=diag):
            break

        # get indices
        xidx=np.around(xii,0).astype('int')+size[1]//2
        yidx=np.around(yii,0).astype('int')+size[0]//2
        idx=[jj for jj in range(len(yidx)) if xidx[jj]>=0 and yidx[jj]>=0\
                and xidx[jj]<=size[1]-1 and yidx[jj]<=size[0]-1]
        xidx=xidx[idx]
        yidx=yidx[idx]

        # skip diagonal jumps
        if len(yidx)>0 and yidx[0]*yidx[-1]<0:
            continue
        xii=xii[idx]
        yii=yii[idx]

        # pick line width from image
        lw=img_layers[yidx,xidx]

        # add random perturbation
        lwran=np.random.random(lw.shape)-0.5
        lw=lw+lwran

        # randomize color
        colorjj=np.random.randint(0,60)*np.ones(3)/float(255)

        for jj in range(len(xii)-1):

            # smooth line widths
            lwjjs=lw[max(0,jj-3):min(len(lw),jj+3)]
            if jj==0 and nc>0:
                lwjjs=np.r_[lwjjs_old,lwjjs]

            wjj=np.ones(len(lwjjs))
            wjj=wjj/len(wjj)
            lwjj=np.dot(lwjjs,wjj)

            ax.plot([xii[jj], xii[jj+1]],
                    [yii[jj], yii[jj+1]],
                    color=colorjj,
                    linewidth=lwjj)

            if jj==len(xii)-2:
                lwjjs_old=lwjjs

        nc+=1

    ax.axis('off')
    #ax.set_facecolor((1.,0.5,0.5))
    ax.set_aspect('equal')
    plt.show()

mikael

@Jason, nice! Creates pretty artsy pictures, in the sense that recognizing the people in the original picture is not likely.

omz

This is really pretty cool! I wonder if it might look even nicer if the whole picture was circular. (I haven't looked at the code in enough detail to see whether this is easy to do or not). But it's really fun to play around with this.