Search code examples
pythonmatplotlibvoxel

Cannot plot voxels using RGBA colours per voxel in matplotlib


I'm trying to use matplotlib's voxel example, but cannot specify RGBA colours.

Code as follows:

import matplotlib.pyplot as plt; import numpy as np  # using matplotlib 3.1.1
from mpl_toolkits.mplot3d import Axes3D  # noqa: F401 unused import


x, y, z = np.indices((12, 12, 12))

# draw cuboids in the top left and bottom right corners, and a link between them
cube1 = (x < 3) & (y < 3) & (z < 3)
cube2 = (x >= 9) & (y >= 9) & (z >= 9)
link = abs(x - y) + abs(y - z) + abs(z - x) <= 2

# combine the objects into a single boolean array
voxels = cube1 | cube2 | link

# From the example - this works
colors = np.empty(voxels.shape, dtype=object)
colors[link] = 'red'
colors[cube1] = 'blue'
colors[cube2] = 'green'

# The following sets the values correctly, but fails if used as the colors param
# colors = np.empty(voxels.shape + (3,), dtype=object)
# colors[link, :] = ((1., 0., 0.))
# colors[cube1, :] = ((0., 1., 0.))
# colors[cube2, :] = ((0., 0., 1.))

fig = plt.figure(figsize=(12, 10), dpi=100)
ax = fig.gca(projection='3d')
ax.voxels(voxels, facecolors=colors, edgecolor='k')
plt.show()

I know that matplotlib should allow something very similar to this, as their own docs state:

facecolors, edgecolors : array_like, optional

The color to draw the faces and edges of the voxels. Can only be passed as keyword arguments. This parameter can be:

    ...

    * A 4D ndarray of rgb/rgba data, with the components along the last axis.

I know that in my case, which doesn't work (throws ValueError: Invalid RGBA argument: 0.0), the size of the colour grid is the same, bar the extra dimension to contain the rgba values. This should work. What am I missing?

Note: not a duplicate of this. Not voxels, not 3D. Different problem.

Actual error dump:

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-95-776efc2fdb99> in <module>
     25 fig = plt.figure(figsize=(12, 10), dpi=100)
     26 ax = fig.gca(projection='3d')
---> 27 ax.voxels(voxels, facecolors=colors, edgecolor='k')
     28 plt.show()

~/py3venv/lib/python3.6/site-packages/mpl_toolkits/mplot3d/axes3d.py in voxels(self, facecolors, edgecolors, shade, lightsource, *args, **kwargs)
   2951             if shade:
   2952                 normals = self._generate_normals(faces)
-> 2953                 facecolor = self._shade_colors(facecolor, normals, lightsource)
   2954                 if edgecolor is not None:
   2955                     edgecolor = self._shade_colors(

~/py3venv/lib/python3.6/site-packages/mpl_toolkits/mplot3d/axes3d.py in _shade_colors(self, color, normals, lightsource)
   1785             shade[~mask] = 0
   1786 
-> 1787             color = mcolors.to_rgba_array(color)
   1788             # shape of color should be (M, 4) (where M is number of faces)
   1789             # shape of shade should be (M,)

~/py3venv/lib/python3.6/site-packages/matplotlib/colors.py in to_rgba_array(c, alpha)
    292     result = np.empty((len(c), 4), float)
    293     for i, cc in enumerate(c):
--> 294         result[i] = to_rgba(cc, alpha)
    295     return result
    296 

~/py3venv/lib/python3.6/site-packages/matplotlib/colors.py in to_rgba(c, alpha)
    175         rgba = None
    176     if rgba is None:  # Suppress exception chaining of cache lookup failure.
--> 177         rgba = _to_rgba_no_colorcycle(c, alpha)
    178         try:
    179             _colors_full_map.cache[c, alpha] = rgba

~/py3venv/lib/python3.6/site-packages/matplotlib/colors.py in _to_rgba_no_colorcycle(c, alpha)
    238         # float)` and `np.array(...).astype(float)` all convert "0.5" to 0.5.
    239         # Test dimensionality to reject single floats.
--> 240         raise ValueError("Invalid RGBA argument: {!r}".format(orig_c))
    241     # Return a tuple to prevent the cached value from being modified.
    242     c = tuple(c.astype(float))

ValueError: Invalid RGBA argument: 0.0

Solution

  • The problem is that you create the array with datatype object. This is fine for strings, but RGB arrays must contain numbers for matplotlib to understand them. So here it is enough to remove dtype=object.