Perlin 2D Noise in python

January 18, 2019 ยท 3 minute read

random reds with perlin

Random is good (and in fact, from random import randint, uniform, normalvariate is pretty much a default for me at this point). White noise, however, is not particularly interesting: uniform random noise

Enters Perlin

Ken Perlin invented a technique to generate noise layer. This more coherent noise turned out to be great to mimic clouds, coasts and whatnots. His webpage at nyu is packed with interesting things in a … questionable design, and his blog is pretty funny.

More to the point, his noise is a staple of generative art and helps create interesting designs (possibly in part because our brains are tricked into looking for a pattern where there is none). For example:

more interesting noise: perlin (source: Neil Blevins )

Python’s noise library

There is a well-maintained, but not overly intuitive library to generate Perlin noise. Install with:

pip install noise

and then from noise import pnoise2 for example.

This very interesting resource on building maps helped me to figure out how to use the library, and is an interesting read. I had a slightly different use case, as I frequently want to apply randomnes to elements in an abstract grid. My function todo so looks like this:

def perlin_array(shape = (200, 200),
			scale=100, octaves = 6, 
			persistence = 0.5, 
			lacunarity = 2.0, 
			seed = None):

    if not seed:

        seed = np.random.randint(0, 100)
        print("seed was {}".format(seed))

    arr = np.zeros(shape)
    for i in range(shape[0]):
        for j in range(shape[1]):
            arr[i][j] = pnoise2(i / scale,
                                        j / scale,
                                        octaves=octaves,
                                        persistence=persistence,
                                        lacunarity=lacunarity,
                                        repeatx=1024,
                                        repeaty=1024,
                                        base=seed)
    max_arr = np.max(arr)
    min_arr = np.min(arr)
    norm_me = lambda x: (x-min_arr)/(max_arr - min_arr)
    norm_me = np.vectorize(norm_me)
    arr = norm_me(arr)
    return arr

A couple notes:

  1. scale is an interesting factor, and effectively with everything else being equal (including seed) will really be the “altitude” from which to see the noise. Playing around with the value is strongly advised.

  2. inputs of pnoise2 have to be between 0 and 1. Otherwise, the function returns 0 without complaining.

  3. octaves mean the number of passes/layers of the algorithm. Each pass adds more detail

  4. persistence means how much more each successive value brings. It’s usually, but not always, better to keep it under 1

  5. lacunarity roughly is the level of detail added per pass. I generally keep it at 2

  6. finally, the seed is an input that is not super documented. However, I print the randint I use so I can write down interesting values after each run (which otherwise would be different each time)

The other cool trick: nditer

A numpy array might look overkill, but it has a really cool benefit: it can be iterated over while returning index (numpy reference).

in our case, we could do something like:

side = 100
arr = perlin_array(shape=(x,y), 
		scale = 10, octaves=5, 
		persistence=0.7, 
		lacunarity=2)

for i in range(arr.shape[0]):
        for j in range(arr.shape[1]):
		grey = 2 * arr[i,j]
            	c.set_source_rgb(grey,grey,grey)
            	c.rectangle(i*side, j*side, side, side)
            	c.fill()

Which will give an more interesting noisy grid. Of course, the it[0] can be scaled and used as an argument for any function.

Suggested musical background

Fabian Tanz’ set at Wonderfruit was really nice. There’s a lot more to Sweden than Ikea.