How do I create a Rubix Cube in python using Matplotlib? - python

I have been trying to make a Rubix Cube in python using Matplotlib. I've struggled to get each face of the cube to have its own colour? How do I do this in Matplotlib?
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
axes = [3, 3, 3]
data = np.ones(axes)
alpha = 0.9
colours = np.empty(axes + [4])
colours[0] = [1, 0, 0, alpha] # red
colours[1] = [0, 0, 1, alpha] # blue
colours[2] = [1, 1, 0, alpha] # yellow
figure = plt.figure()
ax = figure.add_subplot(111, projection = '3d')
ax.voxels(data, facecolors = colours, edgecolors = 'grey')
plt.show()

Related

Plot 3D Grid Data as Heat Map using matplotlib

I tried to visualize data as a cube but want to plot each cell inside the cube.
This is a cube not divided by cell but I want this 3D heatmap
I need to visualize the predicted result in a 3D cube with a heatmap
Here is the code:
%matplotlib notebook
%matplotlib inline
from mpl_toolkits.mplot3d import Axes3D
matplotlib.rcParams['backend'] = "Qt4Agg"
import numpy as np
#each sell has its value
y_pred = ([-3.4942558e+01, -1.0922590e+01, -9.4551229e+00, -2.6156654e+01,
1.1722648e+01, 6.4425749e-01, -1.1141028e+01, 5.9728470e+01,
-4.5249408e-01, -8.7741699e+00, 9.9452820e+00, -2.8595707e+00,
2.6979692e+00, 7.9802217e+00, -1.1204488e+00, -4.5213123e+01,
-7.1909481e-01, -8.4901733e+00, -9.2283611e+00, 5.0730385e+01,
-7.9520082e-01, 1.4589276e+02, 8.4267479e+01, -6.7399621e+00,
2.1536992e+02, 5.0958019e+01, -7.7071385e+00, 8.4650040e+01,
2.5421507e+01, -4.1403370e+00, -8.5559702e+00, 5.5478176e+01,
-1.0955868e+01, 3.1425345e+02, 1.9285686e+02, 4.6105843e+00,
6.6680554e+02, 2.7745572e+02, 1.2241451e+01, 5.1578967e+02,
1.8129390e+02, 7.3322144e+00, 1.2733205e+02, 4.4435711e+01,
-2.0441423e-01, 8.3673248e+01, 1.7386259e+02, -1.6146477e-01,
5.9598431e+02, 5.7501422e+02, 4.7413929e+01, 7.6495886e+02,
6.5124884e+02, 7.9399048e+01, 4.0769174e+02, 3.2358469e+02,
3.6726327e+00, 7.5896362e+01, 9.8999245e+01, -1.7699455e+00,
1.0632815e+02, 2.3123619e+02, 4.5826878e+01, 3.6063211e+02,
5.6004309e+02, 1.3757048e+02, 3.7029037e+02, 5.5426331e+02,
1.0625824e+02, 1.7360068e+02, 2.6583237e+02, 2.0798336e+01,
4.7036118e+01, 7.3350151e+01, 9.1420832e+00])
ex1 = np.trunc(y_pred).reshape(5,5,3)
max_value = np.amax(ex1)
min_value = np.amin(ex1)
average = 1/2*(min_value + max_value)
mean_value = np.mean(ex1)
print(f"max: {max_value}\nmin: {min_value}\naverage: {average}\nmean: {mean_value}")
x = 5
y = 5
z = 3
axes = [5, 5, 3]
data = np.ones(axes, dtype=bool)
alpha = 0.5
colors = np.empty(axes + [4])
for x1 in range(x):
for y1 in range(y):
for z1 in range(z):
if ex1[x1][y1][z1] >= average:
colors[x1][y1][z1] = [1, 0, 0, alpha]
elif ex1[x1][y1][z1] > mean_value:
colors[x1][y1][z1] = [1, 1, 0, alpha]
elif ex1[x1][y1][z1] <= mean_value:
colors[x1][y1][z1] = [0, 0, 1, alpha]
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.voxels(data, facecolors=colors, edgecolors='grey')
ax.axis('on')
ax.set_aspect('auto')
plt.show()
I would really appreciate it if you help me with this

How to determine which cubes the line passes through

I was looking for a way to build cubes of the same size, then draw a line through this space and output the result in the form of coordinates of cubes that this line intersects and paint these cubes with a different color. The line can be either straight or curved.
I used matplotlib to plot cubes and lines. From these sources:
https://www.geeksforgeeks.org/how-to-draw-3d-cube-using-matplotlib-in-python/
Representing voxels with matplotlib
Example code:
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
axes = [5, 5, 5]
# Create Data
data = np.ones(axes, dtype=np.bool)
# Controll Tranperency
alpha = 0.3
# Control colour
colors = np.empty(axes + [4], dtype=np.float32)
colors[0] = [1, 0, 0, alpha] # red
colors[1] = [0, 1, 0, alpha] # green
colors[2] = [0, 0, 1, alpha] # blue
colors[3] = [1, 1, 0, alpha] # yellow
colors[4] = [1, 1, 1, alpha] # grey
# Plot figure
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
x1 = [1, 4]
y1 = [0, 5]
z1 = [0, 5]
ax.plot3D(x1, y1, z1, 'black', linewidth = 5)
# Voxels is used to customizations of
# the sizes, positions and colors.
ax.voxels(data, facecolors=colors, edgecolors='grey')
result
In short: I need to plot a grid of cubes and draw a line through it. After determine which cubes this line intersects.
Is it possible to do this in Matplotlib or do I need to use another library to solve my question?
God, why do I put myself though this.
Anyways, here is an iterative solution because I do not feel like doing linear algebra. I tried and I failed.
# Here be dragons
def linelamb(x,y,z):
return lambda s: [int(i) for i in [x[0]+s*(x[1]-x[0]), y[0]+s*(y[1]-y[0]), z[0]+s*(z[1]-z[0])]]
line = linelamb(x1,y1,z1)
hitboxes = np.zeros(axes)
x,y,z = 0,0,0
for r in [i for i in np.arange(0,1,0.001)]:
xnew,ynew,znew = line(r)
if not (x == xnew and y == ynew and z == znew):
hitboxes[xnew,ynew,znew] = 1
x,y,z = xnew,ynew,znew
ax.voxels(hitboxes, facecolors=[0,0,0,0.5], edgecolors='black');
I spent some extra time to make this more adaptable but my brain stopped working. You might want to adaptively change the step size of the range but good luck.

Image and Voronoi diagram on the same figure

I need to plot a Voronoi tessellation on top of an existing image using scipy.spatial.Voronoi. I have imported an image as a numpy array using matplotlib.pyplot:
img_file = 'my_image.png'
img = plt.imread(os.path.join(data_dir, img_file))
fig = plt.figure()
ax = fig.add_subplot(111)
When I display the image it works ok:
ax.imshow(img)
my initial image
Then I want to add a Voronoi graph (for some points I choose arbitrarily) on it so I do:
points = np.array([[0, 0], [0, 1], [0, 2], [1, 0], [1, 1], [1, 2], [2, 0], [2, 1], [2, 2]])
vor = Voronoi(points)
voronoi_plot_2d(vor, ax=ax)
plt.show()
and I get this:
Failed attempt to overlay the graph on the image
And when I plot just the graph this is what I get:
Voronoi tessellation separately
So, I wanted to draw them on top of each other by using the same axis (ax) but this ended up coloring in the regions of Voronoi instead. Any help with figuring out how to have the image on the background and the Voronoi on top would be much appreciated!
It actually works, i guess the voronoi points need to be chosen properly:
import matplotlib.pylab as plt
import numpy as np
from scipy.spatial import Voronoi, voronoi_plot_2d
import scipy.ndimage as ndimage
img_file = 'bear.png'
img = plt.imread(img_file)
points = []
for i in range(100):
points.append([np.random.uniform(0, img.shape[0]),np.random.uniform(0, img.shape[1])])
points = np.array(points)
vor = Voronoi(points)
fig = plt.figure(figsize=(20,20))
ax = fig.add_subplot(111)
ax.imshow(ndimage.rotate(img, 90))
voronoi_plot_2d(vor, point_size=10, ax=ax)
plt.show()

Plotting randomly stacked cubes in 3D- mplot3d?

I am exploring random stackings of cubes.
I started with 2D and can generate random packings within a rectangle like this:
Now I have the code to generalize the stacking to 3D, but I am struggling to generalize the visualization. An example data set is, filling a 3x3x3 cube with 1x1x1 and 2x2x2 cubes,
#the coordinates of a corner vertex of the 19 1x1x1 cubes
x1 = [1, 0, 2, 0, 0, 0, 2, 1, 0, 1, 2, 2, 0, 0, 0, 2, 0, 1, 1]
y1 = [1, 1, 0, 2, 0, 0, 2, 2, 2, 0, 1, 0, 1, 2, 1, 0, 0, 0, 0]
z1 = [2, 1, 1, 0, 1, 2, 2, 2, 2, 1, 2, 0, 0, 1, 2, 2, 0, 0, 2]
#the coordinates of a corner vertex of the 1 2x2x2 cube
x2 = [1]
y2 = [1]
z2 = [0]
# I believe the random filling is working because
# the total volumes equal: 19 + 2**3 = 3**3
#I would like to start with the lists
X = [x1,x2]
Y = [y1,y2]
Z = [z1,z2]
sizes = [1,2]
#because I want to generalize the visualization to n sizes
So far, all I have the knowledge to do is plot a 3D scatter of the data
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
for ii in range(len(sizes)):
ax.scatter(X[ii],Y[ii],Z[ii])
plt.show()
I would like to make a plot more like this, except with variable sizes.
Any help would be greatly appreciated! I have a lot to learn about matplotlib/pyplot and so on.
I have made a little bit of progress:
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle, PathPatch
from mpl_toolkits.mplot3d import Axes3D
import mpl_toolkits.mplot3d.art3d as art3d
def cube(a,b,c,l):
for zz in [c,c+l]:
for i in ["x","y","z"]:
side = Rectangle((a, b), l,l)
ax.add_patch(side)
art3d.pathpatch_2d_to_3d(side, z=zz, zdir=i)
fig = plt.figure()
ax=fig.gca(projection='3d')
cube(0,0,0,1)
ax.set_xlim3d(-2, 2)
ax.set_ylim3d(-2, 2)
ax.set_zlim3d(-2, 2)
plt.show()
This plots a single cube.
EDIT:
More progress, I am now very close
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle, PathPatch
from mpl_toolkits.mplot3d import Axes3D
import mpl_toolkits.mplot3d.art3d as art3d
cmap = plt.get_cmap('spring') #define the colors of the plot
colors = [cmap(i) for i in np.linspace(0.1, 0.9, n+1)]
def cube(a,b,c,l): #plots a cube of side l at (a,b,c)
for ll in [0,l]:
for i in range(3):
dire= ["x","y","z"]
xdire = [b,a,a]
ydire = [c,c,b]
zdire = [a,b,c]
side = Rectangle((xdire[i], ydire[i]),facecolors[np.where(sizes == l)[0]],edgecolor='black')
ax.add_patch(side)
art3d.pathpatch_2d_to_3d(side, z=zdire[i]+ll, zdir=dire[i])
def plotter3D(X,Y,Z,sizes): #run cube(a,b,c,l) over the whole data set
for iX in range(len(X)):
x = X[iX]
y = Y[iX]
z = Z[iX]
for ix in range(len(x)):
cube(x[ix],y[ix],z[ix],sizes[iX])
fig = plt.figure() #open a figure
ax=fig.gca(projection='3d') #make it 3d
plotter3D(X,Y,Z,sizes) #generate the cubes from the data set
ax.set_xlim3d(0, length) #set the plot ranges
ax.set_ylim3d(0, width)
ax.set_zlim3d(0, height)
plt.show()
This generates the desired output, although it seems to be see-through in some places when viewed from certain angles. You can see this in the small cube-- dead center at coordinates (1.5,2,3) Any idea how to fix this?
Another edit:
The solution outined above has two problems: (1) I can't get equal aspect ratios for the three axes, and (2) The cubes are see-through from certain angles. Here's what the output looks like for a larger system

Transparency for Poly3DCollection plot in matplotlib

I am trying to draw some objects with the fabulous Matplotlib package for Python. These objects consist of points implemented with plt.scatter() and patches implemented with Poly3DCollection. I would like to have the patches with a slight transparency so that the points and edges behind the patches can be seen.
Here the code and plot I already generated. Seems I am almost there, just missing the feature of transparency. Interestingly, if I first plot the Ploy3DCollection and afterwards the scatter points, the points can be seen, but not the edges.
Anyone having a suggestion for me?
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
x = [0, 2, 1, 1]
y = [0, 0, 1, 0]
z = [0, 0, 0, 1]
vertices = [[0, 1, 2], [0, 1, 3], [0, 2, 3], [1, 2, 3]]
tupleList = list(zip(x, y, z))
poly3d = [[tupleList[vertices[ix][iy]] for iy in range(len(vertices[0]))] for ix in range(len(vertices))]
ax.scatter(x,y,z)
ax.add_collection3d(Poly3DCollection(poly3d, facecolors='w', linewidths=1, alpha=0.5))
plt.show()
I made a slight modification to the OP code and got the transparency working. It appears that the facecolors argument of Poly3DCollection overrides the transparency argument, so the solution was to set the color in a separate call to either Poly3DCollection.set_color or Poly3DCollection.set_facecolor:
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
x = [0, 2, 1, 1]
y = [0, 0, 1, 0]
z = [0, 0, 0, 1]
vertices = [[0, 1, 2], [0, 1, 3], [0, 2, 3], [1, 2, 3]]
tupleList = zip(x, y, z)
poly3d = [[tupleList[vertices[ix][iy]] for iy in range(len(vertices[0]))] for ix in range(len(vertices))]
ax.scatter(x,y,z)
collection = Poly3DCollection(poly3d, linewidths=1, alpha=0.2)
face_color = [0.5, 0.5, 1] # alternative: matplotlib.colors.rgb2hex([0.5, 0.5, 1])
collection.set_facecolor(face_color)
ax.add_collection3d(collection)
plt.show()
Interestingly, if you explicitly set the edge color with collection.set_edgecolor('k'), the edges will also honor the transparency setting.
I found a nice workaround: After plotting the data, do another plot on top with the same color and lighter line style. Instead of Poly3DCollection I use Line3DCollection, so no faces are plotted. The result looks very much as anticipated.
See below the new plot and the script creating it.
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d.art3d import Poly3DCollection, Line3DCollection
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
x = [0, 2, 1, 1]
y = [0, 0, 1, 0]
z = [0, 0, 0, 1]
vertices = [[0, 1, 2], [0, 1, 3], [0, 2, 3], [1, 2, 3]]
tupleList = list(zip(x, y, z))
poly3d = [[tupleList[vertices[ix][iy]] for iy in range(len(vertices[0]))] for ix in range(len(vertices))]
ax.scatter(x,y,z)
ax.add_collection3d(Poly3DCollection(poly3d, facecolors='w', linewidths=1, alpha=0.5))
ax.add_collection3d(Line3DCollection(poly3d, colors='k', linewidths=0.2, linestyles=':'))
plt.show()
Thanks a lot Chilichiller and Julian. Your examples are very useful to me at present, because I am working on a little project about 3D representation of matrices with matplotlib, and Poly3DCollection seems suitable for the task.
A little note, that maybe can be useful to future readers.
Running your examples in Python 3 gives TypeError: 'zip' object is not subscriptable.
The simplest solution is to wrap the return value of zip in a call to list() (as indicated by "Dive Into Python 3": http://www.diveintopython3.net/porting-code-to-python-3-with-2to3.html).
Here is a version that uses only one call to Poly3DCollection, where edgecolors='k' controls the color of the line and facecolors='w' controls the color of the faces. Note how Matplotlib colors the edges behind the polygon in a lighter gray color.
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d.art3d import Poly3DCollection, Line3DCollection
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
x = [0, 2, 1, 1]
y = [0, 0, 1, 0]
z = [0, 0, 0, 1]
vertices = [[0, 1, 2], [0, 1, 3], [0, 2, 3], [1, 2, 3]]
tupleList = list(zip(x, y, z))
poly3d = [[tupleList[vertices[ix][iy]] for iy in range(len(vertices[0]))] for ix in range(len(vertices))]
ax.scatter(x,y,z)
ax.add_collection3d(Poly3DCollection(poly3d, edgecolors='k', facecolors='w', linewidths=1, alpha=0.5))
plt.show()
Caution The API for Poly3DCollection is rather confusing: the accepted keyword arguments are all of colors, edgecolor, edgecolors, facecolor, and facecolors (using aliases and decorators to define multiple kwargs to mean the same thing, where the "s" is optional for facecolor and edgecolor).
This bug has been fixed in the new matplotlib. I'm running version 1.5.1.
You can see your version by running python, then doing:
import matplotlib
matplotlib.__version__
You can get matplotlib using pip. If you're on Ubuntu, run this from a terminal:
sudo apt-get install python-pip
sudo pip install matplotlib

Categories