I'm using mayaVI to plot a surface and a vector field in 3D, with the functions mayavi.mlab.surf and mayavi.mlab.quiver3D. These functions do not have many keyword arguments that let me modify the appearance of the surface and quivers, in comparison to the Mayavi pipeline, where I can edit things down to the most miniscule detail (for example quiver arrow head radius - see example figure below). The issue is that once I have made these changes in the mayaVI pipeline, there seems to be no way to save these settings until the next time I want to redraw the figure.
I'm in particular interested in editing Contour properties of the surface, and the Glyph Source properties of the vectors (Shaft radius, Tip radius, Tip length).
Question: Is there an easy way to save the Mayavi pipeline settings until next time, or edit them directly in my Python script (i.e. without using the UI)?
Example:
Code:
#!/usr/bin/env python
import numpy as np
from mayavi import mlab
xmax = 2.0*np.pi
x, y, z = np.mgrid[-xmax:xmax:25j, -xmax:xmax:25j, -xmax:xmax:1j]
v_x = np.sin(x)*np.cos(y)
v_y = np.cos(x)*np.sin(y)
v_z = np.zeros_like(z)
v_abs = np.sqrt(v_x**2 + v_y**2) # scalar field
surf = mlab.surf( x[:,:,0], y[:,:,0], v_abs[:,:,0], colormap='magma' )
obj_j = mlab.quiver3d( x[:,:,0], y[:,:,0], z[:,:,-1], v_x[:,:,0], v_y[:,:,0], v_z[:,:,0], mode='arrow')
mlab.show()
For example, to change the tip length of the arrows,
obj = mlab.quiver3d(..., mode='arrow')
obj.glyph.glyph_source.glyph_source.tip_length = 0.9
There doesn't seem to be any complete documentation of the mayavi pipeline, but one can guess from the GUI interface about the parameters:
Related
I am generating STL files for 3D printing and then using mlab/mayavi to display them. I would like the Z axis to remain vertical as I rotate the image. According to the mayavi documentation, this can be achieved using the following incantation:
fig = mlab.gcf()
from tvtk.api import tvtk
fig.scene.interactor.interactor_style = tvtk.InteractorStyleTerrain()
Unfortunately, as you can see from this screenshot of my app, it is not the Z axis but the Y axis that gets maintained vertical.
This is an issue because 3D printers always think of Z as the vertical axis, so I really need Z and not Y to be oriented vertically. Is there a way in which this can be achieved?
I have experienced the same problem, vertically aligned along the y-axis.
To get the scene to align vertically along the z-axis (which you want), you can simply add a scene.mlab.view() call before setting the interactor.
The scene.mlab.view() call aligns the camera correctly (with z up), before the interactor is set. I have found this solution by simply testing a bunch of stuff, I could not find this "hack" in the documentation.
New code:
fig = mlab.gcf()
from tvtk.api import tvtk
fig.scene.mlab.view(0, 90)
fig.scene.interactor.interactor_style = tvtk.InteractorStyleTerrain()
You can rename them using the Mayavi button to access the pipeline, then selecting the appropiate axes object and changing the labels.
With code, you can add the xlabel, ylabel and zlabel keyword arguments to specify them. This is specified in the axes API
I have a Traits and Mayavi script that presents an mlab scene and several traits editors. The editors affect what data is shown in a surface, quiver3d and legend (Scalar LUT Manager) by calling my drawing method. Each change triggers a clear figure and re-draw.
Learning from the Mlab interactive dialog example the plot3d* uses mlab_source.set to change the data without clearing the figure and re-drawing. In update_plot():
if self.plot is None:
self.plot = self.scene.mlab.plot3d(x, y, z, t, tube_radius=0.025, colormap='Spectral')
else:
self.plot.mlab_source.set(x=x, y=y, z=z, scalars=t)
What my surface and quiver3d calls return are mayavi.modules.surface.Surface and mayavi.modules.vectors.Vectors objects respectively. Surface and LUTManager report no mlab_source: AttributeError: 'Surface'/'LUTManager' object has no attribute 'mlab_source'. Quiver3d reports an mayavi.tools.sources.MGlyphSource
1) How can I change the data/source in my surface and scalar LUTManager?
2) How do I correctly change the quiver’s data/source?
When I attempt to change the values of the quiver I get a TraitError: Cannot set the undefined 'u' attribute of a 'Vectors' object. This puzzles me because I used the six-value initializer.
if self.quiver is None:
self.quiver = self.scene.mlab.quiver3d(xyz[:,0], xyz[:,1], xyz[:,2],
velocity[:,0], velocity[:,1], velocity[:,2],
figure=self.scene.mayavi_scene, scale_factor = self.scale)
else:
self.quiver.mlab_source.set(x = xyz[:,0], y = xyz[:,1], z = xyz[:,2],
u = velocity[:,0], v = velocity[:,1], w = velocity[:,2])
In the example the plot3d returns a mayavi.modules.surface.Surface and its mlab_source object is a mayavi.tools.sources.MLineSource. Searching the docs for MLineSource is fruitless but externally yields Enthought Tool Suite 3.2 results. Are the Tool Suite docs current?
*self.plot, self.surface and self.quiver are declared as variable = Instance(PipelineBase). PipelineBase is imported from mayavi.core.api.
Based on your comments:
The reason there is no reference to an mlab source in surface is because there is no mlab source. Your module just consists of a raw vtk source, which mayavi is perfectly happy to render unadulterated. However you retain a reference to the PolyData and so you could edit the scalars from that. (You could also use mlab.triangular_mesh which very likely does what you want while introducing an intervening TriangularMeshSource to control the vtk PolyData).
You can get to the LUT with surface.module_manager.scalar_lut_manager.
And you should also be able to get to the mlab level source for the vectors as in the comments, I don't know what is the problem with that if there still is one. The use of traitsui should not have an effect -- the problem is probably a conflict between mayavi and your object model. Try setting the trait type of your mayavi objects to be Any.
I am plotting some graphs using R. When I run the program the plot appears and then quickly disappears. How can I make the plot stay?`
I am running the following code found in Dynamic Time Warping in Python
import numpy as np
import rpy2.robjects.numpy2ri
from rpy2.robjects.packages import importr
# Set up our R namespaces
R = rpy2.robjects.r
DTW = importr('dtw')
# Generate our data
idx = np.linspace(0, 2*np.pi, 100)
template = np.cos(idx)
query = np.sin(idx) + np.array(R.runif(100))/10
# Calculate the alignment vector and corresponding distance
alignment = R.dtw(query, template, keep=True)4
plot(alignments)
dist = alignment.rx('distance')[0][0]
print(dist)
Basically the main file is in python, I have installed rpy2, i am remotely connecting into a unix machine. Now the plot shows up but immediately disappears. This only happens to R plots. When I run matplotlib plots they stay(not disappear). so I am wondering whether I have to put some line of code to make the plots "stay". For example like the matlab "holdon".
One solution would be to wait for the user to type "enter" before the program finishes:
raw_input("Please type enter...")
This is also useful with my Matplotlib plots (instead of using pyplot.show(): this closes all the plots automatically).
PS: I just saw that this was suggested in the link from the comments to the original question. I approve. :)
I'm looking for an interactive graphing library for Python.
By "graph", I meant a set of nodes connected by a set of vertices (not a plot of values over x-y axis, nor a grid of pixels).
By "interactive", I meant I can drag-and-drop the nodes around and I need to be able to click on the nodes/vertices and have the library pass the nodes/vertices to my callbacks, which may add/remove nodes/vertices or display information (I cannot load the full graph at startup as the dataset is too large/complex; instead I'll be loading only the necessary slices of data depending on user inputs).
By Python, I meant the programming language Python, the graphing library should have CPython binding. I have Python 2.7 and Python 3.1, but can downgrade to 2.6 if necessary. This language requirement is because the dataset I'm working with only have Python binding.
The graphing library must support directed graph and be able to layout the nodes automatically. I need to put labels on the nodes.
Preferably, the layouting algorithm should place adjacent nodes near each other. It should be able to handle from 100-1000 nodes and about 300-4000 vertices reasonably in my 4 year old laptop (I typically start with around 100 nodes, but the number might expand depending on user input). Preferably it should be a library with not too many dependencies (except perhaps for Gnome). Open source is preferred.
I have already written a simple prototype of my program using Tkinter Canvas, but I need a more serious graphing library to expand the program. I've looked at graphviz and matplotlib, but apparently they're only for working with static graphs and apparently would need significant amount of work to do interactive manipulations (correct me if I'm wrong, I've only looked at them briefly). I've also tried generating the graph to an SVG file and using Inkscape to view it, but it's too slow and takes too much memory and because of the sheer number of vertices it becomes a tangled mess.
Looks like Nodebox might be what you want:
http://nodebox.net/code/index.php/Graph Mac OSX
http://www.cityinabottle.org/nodebox/ Windows (using OpenGL)
The graph object has functionality for mouse interaction as well, bundled
in the graph.events object. It has the
following properties:
graph.events.hovered: None or the node over which the mouse hovers.
graph.events.pressed: None or the node on which the mouse is
pressing down.
graph.events.dragged: None or the node being dragged.
graph.events.clicked: None or the last node clicked.
graph.events.popup: when True, will display a popup window over the
hovered node.
Also came accross Gephi, looks like that might have the functionality you want as well.
http://gephi.org/ Windows, Linux and Mac OSX
Gephi is an interactive visualization
and exploration platform for all kinds
of networks and complex systems,
dynamic and hierarchical graphs.
You should definitely look at the igraph library if you haven't.
It's a powerful library that can handle large graphs and different layout styles. It can also be used for directed graphs and for interactive and non-interactive visualitzations in 2D and 3D according to the list of features. There is also a tutorial.
Update: Another well-known library is NetworkX for which there are Python packages here. Note that the Mac/Windows software Nodebox, recommended by Acorn, uses NetworkX algorithms.
I have the same problem.
In the end, I think nodebox opengl seems to do the trick.
Don't try to use the graph library at the following link
http://nodebox.net/code/index.php/Graph
with nodebox opengl. It doesn't work, that graph library is only compatible with the mac OSX nodebox. But in anycase that is ok because you don't need it.
See for example the following question:
Adding label to an edge of a graph in nodebox opnegl
It shows example code which works for me, the code can be modified so that clicking on a node not only allows you to move the node, but also allows you to modify the graph.
Just delete
label = "Placeholder"
from the code and it works.
EDIT:
I put some more detailed example code here:
Nodebox open GL Graph, size function not recognized. (Ubuntu)
I thought and tried all the solutions given in this question and finally end up with the following solution.
I think the best scalable solution is using interactive mode of Matplotlib together with networkx. The following code segment explain how to display an annotation of a datapoint for a mouse click. Since we are using Networkx, this solution was much more scalable than anticipated.
import networkx as nx
import matplotlib.pyplot as plt
import nx_altair as nxa
from pylab import *
class AnnoteFinder: # thanks to http://www.scipy.org/Cookbook/Matplotlib/Interactive_Plotting
"""
callback for matplotlib to visit a node (display an annotation) when points are clicked on. The
point which is closest to the click and within xtol and ytol is identified.
"""
def __init__(self, xdata, ydata, annotes, axis=None, xtol=None, ytol=None):
self.data = list(zip(xdata, ydata, annotes))
if xtol is None: xtol = ((max(xdata) - min(xdata))/float(len(xdata)))/2
if ytol is None: ytol = ((max(ydata) - min(ydata))/float(len(ydata)))/2
self.xtol = xtol
self.ytol = ytol
if axis is None: axis = gca()
self.axis= axis
self.drawnAnnotations = {}
self.links = []
def __call__(self, event):
if event.inaxes:
clickX = event.xdata
clickY = event.ydata
print(dir(event),event.key)
if self.axis is None or self.axis==event.inaxes:
annotes = []
smallest_x_dist = float('inf')
smallest_y_dist = float('inf')
for x,y,a in self.data:
if abs(clickX-x)<=smallest_x_dist and abs(clickY-y)<=smallest_y_dist :
dx, dy = x - clickX, y - clickY
annotes.append((dx*dx+dy*dy,x,y, a) )
smallest_x_dist=abs(clickX-x)
smallest_y_dist=abs(clickY-y)
print(annotes,'annotate')
# if clickX-self.xtol < x < clickX+self.xtol and clickY-self.ytol < y < clickY+self.ytol :
# dx,dy=x-clickX,y-clickY
# annotes.append((dx*dx+dy*dy,x,y, a) )
print(annotes,clickX,clickY,self.xtol,self.ytol )
if annotes:
annotes.sort() # to select the nearest node
distance, x, y, annote = annotes[0]
self.drawAnnote(event.inaxes, x, y, annote)
def drawAnnote(self, axis, x, y, annote):
if (x, y) in self.drawnAnnotations:
markers = self.drawnAnnotations[(x, y)]
for m in markers:
m.set_visible(not m.get_visible())
self.axis.figure.canvas.draw()
else:
t = axis.text(x, y, "%s" % (annote), )
m = axis.scatter([x], [y], marker='d', c='r', zorder=100)
self.drawnAnnotations[(x, y)] = (t, m)
self.axis.figure.canvas.draw()
df = pd.DataFrame('LOAD YOUR DATA')
# Build your graph
G = nx.from_pandas_edgelist(df, 'from', 'to')
pos = nx.spring_layout(G,k=0.1, iterations=20) # the layout gives us the nodes position x,y,annotes=[],[],[] for key in pos:
x, y, annotes = [], [], []
for key in pos:
d = pos[key]
annotes.append(key)
x.append(d[0])
y.append(d[1])
fig = plt.figure(figsize=(10,10))
ax = fig.add_subplot(111)
ax.set_title('select nodes to navigate there')
nx.draw(G, pos, font_size=6,node_color='#A0CBE2', edge_color='#BB0000', width=0.1,
node_size=2,with_labels=True)
af = AnnoteFinder(x, y, annotes)
connect('button_press_event', af)
show()
So I'm representing a token ring network (doing the simulation in SimPy), I'm a totally newbie to matplotlib, but I was told that it'd be really good for representing my simulation visually.
So I googled around and found out how to draw shapes and lines - using add_patch and add_line respectively to the axes (I believe). So now I have this output which is absolutely fine:
(can't post images yet!!)
http://img137.imageshack.us/img137/7822/screenshot20100121at120.png
But I'm getting this using the pylab.show() function, and what I think I want is to achieve this using the pylab.plot() function so that I can then update it as my simulation progresses using pylab.draw() afterward.
My code is as follows:
plab.ion()
plab.axes()
for circ in self.circleList:
plab.gca().add_patch(circ)
for line in self.lineList:
plab.gca().add_line(line)
plab.axis('scaled')
plab.show()
Where circleList and lineList are lists containing the circles and lines on the diagram
I'm probably misunderstanding something simple here, but I can't actually find any examples that aren't overtly graph based that use the plot() function.
Clarification:
How can I get that same output, using pylab.plot() instead of pylab.show() ?
Replicating your image using the plot method:
from pylab import *
points = []
points.append((-0.25, -1.0))
points.append((0.7, -0.7))
points.append((1,0))
points.append((0.7,1))
points.append((-0.25,1.2))
points.append((-1,0.5))
points.append((-1,-0.5))
points.append((-0.25, -1.0))
a_line = plot(*zip(*points))[0]
a_line.set_color('g')
a_line.set_marker('o')
a_line.set_markerfacecolor('b')
a_line.set_markersize(30)
axis([-1.5,1.5,-1.5,1.5])
show()
EDIT BASED ON COMMENTS
This uses python multiprocessing library to run the matplotlib animation in a separate process. The main process uses a queue to pass data to it which then updates the plot image.
# general imports
import random, time
from multiprocessing import Process, Queue
# for matplotlib
import random
import numpy as np
import matplotlib
matplotlib.use('GTKAgg') # do this before importing pylab
import matplotlib.pyplot as plt
from matplotlib.patches import Circle
def matplotLibAnimate(q,points):
# set up initial plot
fig = plt.figure()
ax = fig.add_subplot(111)
circles = []
for point in points:
ax.add_patch(Circle(point,0.1))
a_line, = ax.plot(*zip(*points))
a_line.set_color('g')
a_line.set_lw(2)
currentNode = None
def animate(currentNode = currentNode):
while 1:
newNode = q.get()
if currentNode: currentNode.remove()
circle = Circle(newNode,0.1)
currentNode = ax.add_patch(circle)
circle.set_fc('r')
fig.canvas.draw()
# start the animation
import gobject
gobject.idle_add(animate)
plt.show()
#initial points
points = ((-0.25, -1.0),(0.7, -0.7),(1,0),(0.7,1),(-0.25,1.2),(-1,0.5),(-1,-0.5),(-0.25, -1.0))
q = Queue()
p = Process(target = matplotLibAnimate, args=(q,points,))
p.start()
# feed animation data
while 1:
time.sleep(random.randrange(4))
q.put(random.sample(points,1)[0])
Of course, after doing this I think you'll be better served with whatnick's image solution. I'd create my own GUI and not use matplotlibs built in widget. I'd then "animate" my GUI by generating PNGs and swapping them.
It sounds like Mark has the answer you were looking for, but if you decide to go with whatnick's approach and build an animation from individual pngs, here is the code to implement Amit's suggestion to use mencoder (from http://en.wikibooks.org/wiki/Mplayer):
mencoder mf://*.png -mf w=400:h=400 -ovc lavc -lavcopts vcodec=xvid -of avi -o output.avi
The core technique is to update the data of the elements being rendered using set_data. Then call draw(). See if your circle and line elements have set_data functions. Otherwise you can use pyvtk. The other option is to render and save the plots to png files and later build an animation from those.