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
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
.