I am trying to visualize a 3D graph using Mayavi.
During the program run, some nodes or edges in the graph become unavailable and I want to visualize dynamically that they become inaccessible in the visualization scene. How can I achieve this?
I'm still a newbie in python but it seems that the Mayavi scene can't be changed through the program once it is displayed.
It can be changed in many ways. You can add and remove elements, change background and foreground colors, and animate stuff. For example (from this link):
from __future__ import absolute_import, division, print_function
from mayavi import mlab
import numpy as np
import math
alpha = np.linspace(0, 2*math.pi, 100)
xs = np.cos(alpha)
ys = np.sin(alpha)
zs = np.zeros_like(xs)
mlab.points3d(0,0,0)
plt = mlab.points3d(xs[:1], ys[:1], zs[:1])
#mlab.animate(delay=100)
def anim():
f = mlab.gcf()
while True:
for (x, y, z) in zip(xs, ys, zs):
print('Updating scene...')
plt.mlab_source.set(x=x, y=y, z=z)
yield
anim()
mlab.show()
, will return you an animation where two sphere exist and one is having it's position changed every step of time:
The Mayavi documentation isn't exactly brilliant from my point of view but you can get some information both from the examples and chapters. For example remove an object from Mayavi pipeline.
Related
I am trying to reproduce the left plot of this animation in python using matplotlib.
I am able to generate the vector arrows using the 3D quiver function, but as I read here, it does not seem possible to set the lengths of the arrows. So, my plot does not look quite right:
So, the question is: how do I generate a number of 3D arrows with different lengths? Importantly, can I generate them in such a way so that I can easily modify for each frame of the animation?
Here's my code so far, with the not-so-promising 3D quiver approach:
import numpy as np
import matplotlib.pyplot as plt
import mpl_toolkits.mplot3d.axes3d
ax1 = plt.subplot(111,projection='3d')
t = np.linspace(0,10,40)
y = np.sin(t)
z = np.sin(t)
line, = ax1.plot(t,y,z,color='r',lw=2)
ax1.quiver(t,y,z, t*0,y,z)
plt.show()
As Azad suggests, an inelegant, but effective, solution is to simply edit the mpl_toolkits/mplot3d/axes3d.py to remove the normalization. Since I didn't want to mess with my actual matplotlib installation, I simply copied the axes3d.py file to the same directory as my other script and modified the line
norm = math.sqrt(u ** 2 + v ** 2 + w ** 2)
to
norm = 1
(Be sure to change the correct line. There is another use of "norm" a few lines higher.) Also, to get axes3d.py to function correctly when it's outside of the mpl directory, I changed
from . import art3d
from . import proj3d
from . import axis3d
to
from mpl_toolkits.mplot3d import art3d
from mpl_toolkits.mplot3d import proj3d
from mpl_toolkits.mplot3d import axis3d
And here is the nice animation that I was able to generate (not sure what's going wrong with the colors, it looks fine before I uploaded to SO).
And the code to generate the animation:
import numpy as np
import matplotlib.pyplot as plt
import axes3d_hacked
ax1 = plt.subplot(111,projection='3d')
plt.ion()
plt.show()
t = np.linspace(0,10,40)
for index,delay in enumerate(np.linspace(0,1,20)):
y = np.sin(t+delay)
z = np.sin(t+delay)
if delay > 0:
line.remove()
ax1.collections.remove(linecol)
line, = ax1.plot(t,y,z,color='r',lw=2)
linecol = ax1.quiver(t,y,z, t*0,y,z)
plt.savefig('images/Frame%03i.gif'%index)
plt.draw()
plt.ioff()
plt.show()
Now, if I could only get those arrows to look prettier, with nice filled heads. But that's a separate question...
EDIT: In the future, matplotlib will not automatically normalize the arrow lengths in the 3D quiver per this pull request.
Another solution is to call ax.quiever on each arrow, individually - with each call having an own length attribute. This is not very efficient but it will get you going.
And there's no need to change MPL-code
I have a code in python to render a few spheres in python that looks like this:
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
import random
import mayavi
from mayavi import mlab
N = 4;
diams = .4*np.ones([N]);
xvals = np.arange(N);
yvals = np.zeros(N);
zvals = np.zeros(N);
pts = mlab.points3d(xvals, yvals, zvals, diams, scale_factor=1,transparent=True)
mlab.show()
The default view of the figure adds distortion based on the camera position (farther spheres smaller). I'd like to set the to parallel projection (farther spheres same size) by some command so it renders like this automatically.
I didn't find a straightforward solution with google or the documentation. Thanks!
Try setting fig.scene.parallel_projection = True or mlab.gcf().scene.parallel_projection = True in your case.
As a quick example, compare (zoomed in to magnify differences):
import numpy as np
from mayavi import mlab
np.random.seed(1977)
x, y, z = np.random.random((3, 10))
mlab.points3d(x, y, z)
mlab.show()
And when we set an orthogonal projection:
import numpy as np
from mayavi import mlab
np.random.seed(1977)
x, y, z = np.random.random((3, 10))
mlab.points3d(x, y, z)
mlab.gcf().scene.parallel_projection = True
mlab.show()
In addition to the accepted answer, I discovered that when we use the figure.scene.parallel_projection = True mode, the parameters returned by mlab.view() are no longer sufficient to describe the camera view completely. There is another parameter that comes into play:
figure.scene.camera.parallel_scale
So, if one wishes to set the view to be the same every time, then they have to (1) set mlab.view(..) and also (2) set figure.scene.camera.parallel_scale = 5.0 for example.
(Background story: my script plots a surface, then I had set the camera using only mlab.view(..), and saw that the rendered images had inconsistent scaling. The reason is: as I plot, TVTK updates the camera's parameters, so they might be different if the plots are not identical. These parameters include parallel_scale, which affects the projection — it's basically a zoom — but is independent of mlab.view().)
I try to render the classic rice.png image for an image processing class but I cannot get the result I want.
import matplotlib.pyplot as plt
import numpy as np
from scipy import misc
from matplotlib import cm
from mpl_toolkits.mplot3d import Axes3D
# rice.png is the image one can find on the internet
rice = misc.imread('rice.png')
height, width = rice.shape
fig = plt.figure(2)
ax = fig.gca(projection='3d')
X = np.arange(0, height, 1)
Y = np.arange(0, width, 1)
X, Y = np.meshgrid(X, Y)
surf = ax.plot_surface(X, Y, rice, cmap=cm.coolwarm, linewidth=0)
plt.title('3D representation of image')
plt.show()
But that gives me this :
I tried using set_zticks but the ticks overflow as in the image above, I tried also the solutions one can see here but it overflows also and/or give poor result.
My goal is to have something like what can be seen in the paragraph how can I do so ?
[edit] I have already seen this question that gives a less complete answer that what can be found in the other link I gave earlier (idea of overriding the proj fonction).
However, I am not happy with the results. First because it means I have to change a functions in the matplotlib library (if I follow the SO solution) and as I will share my code with other students from my class, I do not want to do so. Then because it does not give me the same result (see later) it does not center the image, it just change the scale then cut the above part.
[edit2] update of the code
I know we can create simple 3-Dimensional spheres using matplotlib, an example of such a sphere is included in the documentation.
Now, we also have a warp method as part of the matplotlib module, an example of it's usage is here .
To warp a cylindrical image to the sphere. Is it possible to combine these methods to create a 3D rotatable earth? Unless my way of thinking about this problem is way off it seems that to be able to do this you would have to take the pixel data of the image and then plot every pixel using the sin and cosine expressions along the surface of the 3D sphere being created in the first example. Some examples of these cylindrical maps can be found here
I know alternative ways to do this are through maya and blender, but I am attempting to stay within matplotlib to do this, as I want to create this plot and then be able to plot geospatial data to the surface using an array of data.
Interesting question. I tried to basically follow the thinking outlined by #Skeletor, and map the image so that it can be shown with plot_surface:
import PIL
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
# load bluemarble with PIL
bm = PIL.Image.open('bluemarble.jpg')
# it's big, so I'll rescale it, convert to array, and divide by 256 to get RGB values that matplotlib accept
bm = np.array(bm.resize([d/5 for d in bm.size]))/256.
# coordinates of the image - don't know if this is entirely accurate, but probably close
lons = np.linspace(-180, 180, bm.shape[1]) * np.pi/180
lats = np.linspace(-90, 90, bm.shape[0])[::-1] * np.pi/180
# repeat code from one of the examples linked to in the question, except for specifying facecolors:
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
x = np.outer(np.cos(lons), np.cos(lats)).T
y = np.outer(np.sin(lons), np.cos(lats)).T
z = np.outer(np.ones(np.size(lons)), np.sin(lats)).T
ax.plot_surface(x, y, z, rstride=4, cstride=4, facecolors = bm)
plt.show()
Result:
Here what I made some hours ago:
First we import the needed libraries:
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
import imageio
Secondly, we make the figures and stored them as png in our directory:
Note that I wrote range(0,330,20)
for i in range(0,330,20):
my_map = Basemap(projection='ortho', lat_0=0, lon_0=i, resolution='l', area_thresh=1000.0)
my_map.bluemarble()
my_map.etopo()
name=str(i)
path='/path/to/your/directory/'+name
plt.savefig(path+'.png')
plt.show()
plt.clf()
plt.cla()
plt.close()
And finally we can join all the images in an animated GIF:
images = []
for f in range(0,330,20):
images.append(imageio.imread("/path/to/your/directory/"+str(f)+".png"))
imageio.mimsave('movie.gif', images, duration=0.5)
and then enjoy the result:
I could imagine the following solution:
Using numpy.roll you could shift your array by one column (ore more) with each call. So you could load your image of the earth surface into a numpy array as a template and export the rotated image into a jpg. This you plot as shown in the warp example.
I've came across this simple example that would help me with my problem:
"""
Pyplot animation example.
The method shown here is only for very simple, low-performance
use. For more demanding applications, look at the animation
module and the examples that use it.
"""
import matplotlib.pyplot as plt
import numpy as np
x = np.arange(6)
y = np.arange(5)
z = x * y[:,np.newaxis]
for i in xrange(5):
if i==0:
p = plt.imshow(z)
fig = plt.gcf()
plt.clim() # clamp the color limits
plt.title("Boring slide show")
else:
z = z + 2
p.set_data(z)
print "step", i
plt.pause(0.5)
This shows animation in pyplot interface, but I'd like to save this animation in some movie format, is there a way?
One way is to save all the steps as images and then make them into a movie with e.g. ffmpeg.
Another way to save a matplotlib animation as a video is explained in this article http://jakevdp.github.com/blog/2012/08/18/matplotlib-animation-tutorial/. It shows a higher level solution where you specify the animation as a draw function that changes with time. Also you don't have to deal with saving each frame.