In [1]:
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

plotting images

We can easily plot images by using the imshow function. Conveniently, an image can just be a 2-dimensional numpy array of floats in the range of 0 to 1. We can easily create such an array with the ranf function. Below we create a 128x128 image of random values and display that using the ocean colormap from the pyplot.cm module.


In [2]:
img = np.random.ranf((128,128))
plt.imshow(img, cmap=plt.cm.ocean)


Out[2]:
<matplotlib.image.AxesImage at 0xc8b19d5d68>

value noise

The image above is an example of random noise. Although useful we need something a bit more controllable. For this we need a function that produces value noise instead of just random noise. Essentially this is just a function that given a vector of inputs, produces an float in the range of 0 to 1. For now we'll focus on one-dimensional noise but we'll deal with noise in more dimensions later.

There's various ways to produce value noise but we're gonna use a method that utilizes a seeded vector of random values. For no particular reason we're going to call this vector r and we'll use a numpy array to hold it. For convenience we will also write a function seed that we can use to initialize the table.


In [3]:
def seed(n, shape=(8,)):
    global r
    np.random.seed(n)
    r = np.random.ranf(shape)

We can visualize r by plotting it.


In [4]:
seed(0)
x = np.arange(0, len(r), 1)
plt.plot(x, r[x], 'bo')
plt.axis('tight')


Out[4]:
(0.0, 7.0, 0.42365479933890471, 0.89177300078207977)

We can use this table to define a function noise that given an integer value x will return a value in the range of 0 to 1. The r vector only has a limited amount of values. In order to make it work for any value of x we can perform a modulo operation with the length of the r vector.


In [5]:
def noise(x):
    x = int(x % len(r))
    return r[x]

By default we only have 8 values in the r vector so if we plot the noise function over a range 0 to 16 we can see that it repeats itself halfway.


In [6]:
x = np.arange(0, 16, 1)
y = [noise(x) for x in x]
plt.plot(x, y, 'bo')


Out[6]:
[<matplotlib.lines.Line2D at 0xc8b1c2a390>]

Our noise function works for integer values of x ideally it works for real values of x as well. We can do this by interpolation.


In [7]:
def noise(x):
    xi = int(x)
    x0 = xi % len(r)
    x1 = 0 if x0 == (len(r) - 1) else (x0 + 1)
    v0, v1 = r[x0], r[x1]
    t = x - xi
    return np.interp(t, [0, 1], [v0, v1])

In [8]:
x = np.arange(0, 16, 1/10)
y = [noise(x) for x in x]
plt.plot(x, y)


Out[8]:
[<matplotlib.lines.Line2D at 0xc8b249d438>]

Now let's create some noise in two-dimensions. We'll implement a naive solution at first and revisit it later. Let's start by re-seeding the r table in two dimensions.


In [9]:
seed(0, (4,4))

And now we redefine our noise function so it will interpolate between the x and y value as well.


In [10]:
def noise(x, y):
    xi, yi = int(x), int(y)
    tx, ty = x - xi, y - yi
    x0 = xi % len(r)
    x1 = 0 if x0 == (len(r) - 1) else x0 + 1
    y0 = yi % len(r[0])
    y1 = 0 if y0 == (len(r) - 1) else y0 + 1
    c00 = r[x0][y0]
    c01 = r[x1][y0]
    c10 = r[x0][y1]
    c11 = r[x1][y1]
    n0 = np.interp(tx, [0, 1], [c00, c01])
    n1 = np.interp(tx, [0, 1], [c10, c11])
    return np.interp(ty, [0, 1], [n0, n1])

In [11]:
img = np.array([[noise(x, y) for x in range(16)] for y in range(16)])
plt.imshow(img, plt.cm.ocean)


Out[11]:
<matplotlib.image.AxesImage at 0xc8b1a8d160>

We can see clearly see a pattern here. That's because the dimensions of our r table are very small, only 4 in each axis. We'll get much better noise if we make it bigger.


In [12]:
seed(0, (16,16))
img = np.array([[noise(x, y) for x in range(32)] for y in range(32)])
plt.imshow(img, cmap=plt.cm.ocean)


Out[12]:
<matplotlib.image.AxesImage at 0xc8b25082b0>

There's still a pattern but that is to be expected with our still reasonably small r vector. It does like a lot better already but another thing that is noticeable is that it appears to be a bit blocky. This is because of the linear interpolation we're using to calculate the final noise values. We can fix this by introducing the smoothstep function.

smoothstep


In [13]:
def smoothstep(t):
    return 3 * t**2 - 2 * t**3

In [14]:
x = np.linspace(0, 1, 1000)
y = smoothstep(x)
plt.plot(x, y)


Out[14]:
[<matplotlib.lines.Line2D at 0xc8b256acc0>]

And now we have to adjust our noise function to incorporate the smoothstep function.


In [15]:
def noise(x, y, c = lambda t: t):
    xi, yi = int(x), int(y)
    tx, ty = x - xi, y - yi
    x0 = xi % len(r)
    x1 = 0 if x0 == (len(r) - 1) else x0 + 1
    y0 = yi % len(r[0])
    y1 = 0 if y0 == (len(r) - 1) else y0 + 1
    c00 = r[x0][y0]
    c01 = r[x1][y0]
    c10 = r[x0][y1]
    c11 = r[x1][y1]
    sx = c(tx)
    sy = c(ty)
    n0 = np.interp(sx, [0, 1], [c00, c01])
    n1 = np.interp(sx, [0, 1], [c10, c11])
    return np.interp(sy, [0, 1], [n0, n1])

Note that instead of interpolating on tx and ty directly we are using the smoothstep-ed values of sx and sy instead.


In [32]:
def gen_im(size=(64,64), c = lambda x: x):
    w, h = size
    pixels = [[noise(x, y, c) for x in range(w)] for y in range(h)]
    return np.array(pixels)

In [35]:
seed(1, (32, 32))
img = gen_im(c = smoothstep)
plt.imshow(img, cmap=plt.cm.ocean)


Out[35]:
<matplotlib.image.AxesImage at 0xc8ab0a77f0>

In [42]:
MAX_VERTICES = 32
MAX_VERTICES_MASK = MAX_VERTICES - 1

In [ ]: