Matplotlib "gray" colormap doesn't span full black to white range

2.5k views Asked by At

The following code produces a circular pattern:

import numpy as np
import matplotlib.pyplot as mp

def sphere_depth(x, y, depth, radius):
  squ = x**2 + y**2
  rsqu = radius**2
  squ[squ > rsqu] = rsqu
  res = np.sqrt(rsqu - squ)
  res -= (radius - depth)
  res[res < 0.] = 0.
  return res

y_pix = x_pix = 100.

c_steps = 10

x, y = np.mgrid[0:x_pix:1, 0:y_pix:1]
z = sphere_depth(x - x_pix / 2, y - y_pix / 2, 5., 100.)
lvls = np.linspace(z.min(), z.max(), c_steps)

mp.close(1)
fig = mp.figure(1)
mp.axes([0, 0, 1, 1], frameon=False)
mp.contourf(x, y, z, cmap=mp.cm.gray, levels=lvls)
mp.axis('off')
mp.savefig("test.png")

The colormap is set to "gray" and I expect that the minimum value corresponds to black and the maximum value to white. While the later is true, the former doesn't hold for this example. The lowest value is rather dark gray. This can be adjusted when increasing c_steps, but I need to have a very coarse grained gray color map. Thanks for any ideas how to start with a black color and end with white.

1

There are 1 answers

2
Joe Kington On BEST ANSWER

contourf behaves a bit differently than imshow or pcolormesh in this case. It's deliberate for consistency with contour, and due to the way the levels are defined.

The color for each level range is defined by the midpoint of that range. (Also, your center contour isn't actually completely white, but it's visually identical to it.)

To specify that you want the first interval filled with "pure" black, set vmin=mean(lvls[:1]) in your example.

As an example, based on your excellent example in your question:

import numpy as np
import matplotlib.pyplot as plt

def sphere_depth(x, y, depth, radius):
  squ = x**2 + y**2
  rsqu = radius**2
  squ[squ > rsqu] = rsqu
  res = np.sqrt(rsqu - squ)
  res -= (radius - depth)
  res[res < 0.] = 0.
  return res

y_pix = x_pix = 100.

c_steps = 10

x, y = np.mgrid[0:x_pix:1, 0:y_pix:1]
z = sphere_depth(x - x_pix / 2, y - y_pix / 2, 5., 100.)
lvls = np.linspace(z.min(), z.max(), c_steps)

fig = plt.figure()
ax = fig.add_axes([0, 0, 1, 1], frameon=False)
ax.contourf(x, y, z, levels=lvls, cmap=plt.cm.gray, 
            vmin=np.mean(lvls[:2]), vmax=np.mean(lvls[-2:]))
ax.axis('off')

plt.show()

enter image description here

Just for comparison, here's the image from your original example:

enter image description here

It's subtle, but the first one has "pure" black at the edges, and the second one doesn't.