Simple way to do a 3d plot interactively using Mayavi? - python

Reading Mayavi's documentation "http://docs.enthought.com/mayavi/mayavi/building_applications.html" gets me real confused.
My question is, if I just want a 3d plot that allows user interaction, for example, dragging a slider and change the content of the plot, I don't have to use Traits, right? By the documentation, the standard procedure is something like:
class visual(HasTraits):
scene = ...
view = View(...)
#on_trait_change(...)
def do_something()
...
I don't know what traits are. I don't even understand what's an attribute in a class that's defined outside of the constructor (it's a 'trait'?).
Back to the original question, if I just wanted a 3d plot that's changeable, why don't I just directly do it? A working example is as follows:
import numpy as np
from mayavi import mlab
from matplotlib import pyplot as plt
from matplotlib.widgets import Slider
def slider_changed(val):
s.mlab_source.scalars = np.asarray(x * (val + 1), 'd')
# mayavi 3d plot
x, y = np.mgrid[0:3:1,0:3:1]
s = mlab.surf(x, y, np.asarray(x*0.1, 'd'))
# a matplotlib slider
plt.figure()
ax = plt.subplot(1, 1, 1)
slider = Slider(ax, valmin=0., valmax=1., label='test')
slider.on_changed(slider_changed)
plt.show()
mlab.show()
Seems to me if I don't care about embedding the 3d plot in applications, this is a much simpler way to do it? A lot of the attributes of the 3d plot is manipulable through the attributes of mlab_source. I am using a matplotlib slider here as an example, but it seems it can be a pyqt UI or whatever.
However, the documentation says
All the different properties of the pipeline and pipeline objects are expressed as Traits, i.e. special attributes that can be visualized in dialogs and that fire callbacks when they are modified. In particular this means that when a visualization object is modified, the scene can update automatically.
Does it mean if I wanted something that's automatically updated, I have to use Traits in the way it was described above?

Possibly a good start understanding traits would be this article. Those traits, we are talking about here, have been developped by Enthough. It is worth noting that there are also other objects/concepts called traits, which are completely unrelated to what is relevant for using mayavi.
I don't have to use Traits, right?
You are using Traits when calling s.mlab_source.scalars = np.asarray(x * (val + 1), 'd'). The fact that Mayavi updates the plot simply because you change the underlying data is a result of Traits being used.
Mayavi simply uses those traits, instead of usual objects. So once you use mayavi, you inevitably use traits.
Does it mean if I wanted something that's automatically updated, I have to use Traits in the way it was described above?
No, you gave the counter example yourself. You do not have to subclass HasTraits to update a plot. You may use whatever other solution you can think of. However, once you want to embed a mayavi scene into a GUI, it is probably a good idea to do it the way the documentation states it.
Also, subclassing HasTraits is actually a very neat way to easily obtain an interactive figure. So the example from the question could look like
import numpy as np
from mayavi import mlab
from traits.api import HasTraits, Range, Instance,on_trait_change
from traitsui.api import View, Item, Group
from mayavi.core.ui.api import MayaviScene, SceneEditor, MlabSceneModel
x, y = np.mgrid[0:3:1,0:3:1]
class MyModel(HasTraits):
slider = Range(-5., 5., 0.5, )
scene = Instance(MlabSceneModel, ())
def __init__(self):
HasTraits.__init__(self)
self.s = mlab.surf(x, y, np.asarray(x*1.5, 'd'), figure=self.scene.mayavi_scene)
#on_trait_change('slider')
def slider_changed(self):
self.s.mlab_source.scalars = np.asarray(x * (self.slider + 1), 'd')
view = View(Item('scene', editor=SceneEditor(scene_class=MayaviScene)),
Group("slider"))
my_model = MyModel()
my_model.configure_traits()
The nice thing to notice here is that you do not actually define an explicit callback (i.e. there is no on_slider_change or similar).

Related

How can the following: "axes.yaxis.set_major_formatter(FuncFormatter(f))" work since yaxis should be an attribute of AXIS and not of AXES objects?

Practicing on visualization as a Python newbie I have encountered this conceptual issue that got me thinking,
Infact I managed to change the price format on the y axis of a boxplot , from scientific notation to something more clear. Here the outputs before and after the formatting of the y axis
before
after
boxy=sns.boxplot(x="waterfront", y="price", data=df)
# my experiments success
from matplotlib.ticker import FuncFormatter
f = lambda x, pos: f'{x:.0f}'
boxy.yaxis.set_major_formatter(FuncFormatter(f))
the problem is that I realized that the attribute yaxis should refer to an AXIS object, meanwhile here what i call 'boxy' is an AXES object (at least from the seaborn documentation)
Can anyone explain it?
You're right saying that seaborn boxplot returns a matplotlib Axes object. And referring to this answer, we see Axes and Axis objects are different.
Code inspection isn't needed... but under the hood, seaborn uses matplotlib, it is noted in the GitHub here for boxplots.
when you call sns.boxplot part of drawing your plot creates Axis objects... which are objects of the matplotlib.axis module.
The y axis is in fact the first part of boxy.yaxis.set_major_formatter(FuncFormatter(f))
it is accessed with boxy.yaxis. On which you are calling the function .set_major_formatter(FuncFormatter(f)).
To see this, yaxis = boxy.get_yaxis() will return the yaxis of the boxy axes object.
EDIT IN RESPONSE TO COMMENT:
Again you're correct in the comment that this is not documented from what I could find... but if we look in the matplotlib GitHub here, we see in the YAxis class declaration:
class YAxis(Axis):
__name__ = 'yaxis'
It is just 'YAxis' renamed. Classes will assume their name in the declarative line, unless you re-specify using __name__ which was done here!
It exists!!!
boxy's yaxis inherets the set_major_formatter method from its base class, the 'Axis' class. Just to confirm this hierarchy try looking with method resolution order:
print(type(boxy.get_yaxis()).__mro__)
Should display:
(<class 'matplotlib.axis.YAxis'>, <class 'matplotlib.axis.Axis'>, <class 'matplotlib.artist.Artist'>, <class 'object'>)

How to rotate actor on tvtk rendered scene with Mayavi in Python?

I played a bit with Mayavi and particularly with tvtk but I struggle finding examples in which glyphs are placed on the scene at different orientation than default.
Based on this example I prepared the following scene with two cyllinders, one red and one blue,
import mayavi.mlab as mlab
from tvtk.api import tvtk
from tvtk.common import configure_input_data
v = mlab.figure()
# Create two cyllinders
cyl1 = tvtk.CylinderSource(center=(0, 0, 0), radius=1.0, height=0.5, capping=True, resolution=24)
cyl2 = tvtk.CylinderSource(center=(3, 0, 0), radius=1.0, height=0.5, capping=True, resolution=24)
# The mapper converts them into position in 3D
cylinder_mapper1 = tvtk.PolyDataMapper()
configure_input_data(cylinder_mapper1, cyl1.output)
cyl1.update()
cylinder_mapper2 = tvtk.PolyDataMapper()
configure_input_data(cylinder_mapper2, cyl2.output)
cyl2.update()
# Assign them differrent colors
p1 = tvtk.Property(opacity=1.0, color=(1, 0, 0))
p2 = tvtk.Property(opacity=1.0, color=(0, 0, 1))
# The actor is the actually object in the scene.
cyl1_actor = tvtk.Actor(mapper=cylinder_mapper1, property=p1)
v.scene.add_actor(cyl1_actor)
cyl2_actor = tvtk.Actor(mapper=cylinder_mapper2, property=p2)
v.scene.add_actor(cyl2_actor)
# Choose a view angle, and display the figure
mlab.view(90, 45, 7.5, [1.5, 0, 0])
mlab.savefig(filename='cylinders.png')
and what I am trying to achieve here is change the orientation of one of them. Either by providing rotation matrix, either by calling a method, doesn't matter as long as I can somehow rotate the glyph independently (and not just rotate entire scene).
The above example renders
Regarding other questions on that have been previously asked, this one seems useful, yet instead of rotating the actor it rotates entire scene about Y-axis. There also is an example in the scipy-cookbook but again - only with default orientation.
The tvtk documentation is not rich in examples and it is almost hard to believe that there is no example that involves alternate orientation. The documentation only states that classes and methods correspond to their C++ counterparts following some conventions.
tvtk class names are essentially similar to VTK classes except there is no annoying ‘vtk’ at the front. The only difficulty is with classes that start with a digit. For example ‘vtk3DSImporter’ becomes ‘3DSImporter’. This is illegal in Python and therefore the class name used is ‘ThreeDSImporter’. So, if the first character is a digit, it is replaced by an equivalent non-digit string. There are very few classes like this so this is not a big deal.
tvtk method names are enthought style names and not CamelCase. That is, if a VTK method is called AddItem, the equivalent tvtk name is add_item. This is done for the sake of consistency with names used in the enthought package.
Many VTK methods are replaced by handy properties. In the above example, we used m.input = cs.output and p.representation = ‘w’ instead of what would have been m.SetInput(cs.GetOutput()) and p.SetRepresentationToWireframe() etc. Some of these properties are really traits.
Unlike VTK objects, one can set the properties of a tvtk object when the object is initialized by passing the properties (traits) of the object as keyword arguments at the time of class instantiation. For example cs = tvtk.ConeSource(radius=0.1, height=0.5).
Maybe someone who has extensively used VTK with C++ could find that useful but for me it is not very helpful. Nevertheless, I tried to look up the VTK C++ documentation and it did give me some clues. The vtkActor class does inherit some types from vtkProp3D and vtkProp. The vtkProp3D does seem to admit methods and attributes related to object orientation on the scene but it is unclear to me how to set them. The tvtk library is just a wrapper on top of C++ one, which makes it impossible to just inspect what attributes are accepted.
I think for the sake of the internet, maybe we should prepare a proper example of rendering a scene with Mayavi and tvtk that admits both positions of actors and their orientations. Most examples ignore orientations and use spheres so orientation seems not relevant.
Let's summarize.
If someone provides a link to a proper example of rendering a scene with Mayavi and tvtk that involves multiple glyphs with different positions and orientations I will accept such answer.
Making a custom example for (1) will also meet acceptance.
Maybe someone could also comment a bit on the difference between positions of the glyph and the actor that maps this glyph on the scene. As you can see in the examples when I create CylinderSource I explicitly specify where the center is location. This is confusing because is should be the actors center that determines the glyphs position on the scene.
Thanks a lot!
Ok, it turned out Actor accepts orientation argument which is in degrees (not in radians!). Also, the position of the actor has to be specified when actor is created and not when glyph is created!
cyl1_actor = tvtk.Actor(position=(0, 0, 0), mapper=cylinder_mapper1, property=p1, orientation=(0, 0, 90))
renders
I found it by trial and error... The tvtk code in Mayavi package is generated so it is pointless to try to find what attributes are accepted by studying the GitHub repository. Also, you might confuse the tvtk's Actor with Mayavi's Actor which does not have orientation property. Oh and there is actually a property names "property" so try to search for that and get thousands of results as the word property is extensively used everywhere...
Edit:
I noticed I have been specifying positions of actors in the glyph center which was wrong! Here entire source updated:
import mayavi.mlab as mlab
from tvtk.api import tvtk
from tvtk.common import configure_input_data
v = mlab.figure()
# Create two cyllinders
cyl1 = tvtk.CylinderSource(center=(0, 0, 0), radius=1.0, height=0.5, capping=True, resolution=24)
cyl2 = tvtk.CylinderSource(center=(0, 0, 0), radius=1.0, height=0.5, capping=True, resolution=24)
# The mapper converts them into position in 3D
cylinder_mapper1 = tvtk.PolyDataMapper()
configure_input_data(cylinder_mapper1, cyl1.output)
cyl1.update()
cylinder_mapper2 = tvtk.PolyDataMapper()
configure_input_data(cylinder_mapper2, cyl2.output)
cyl2.update()
# Assign them differrent colors
p1 = tvtk.Property(opacity=1.0, color=(1, 0, 0))
p2 = tvtk.Property(opacity=1.0, color=(0, 0, 1))
# The actor is the actually object in the scene.
cyl1_actor = tvtk.Actor(position=(0, 0, 0), mapper=cylinder_mapper1, property=p1)
v.scene.add_actor(cyl1_actor)
cyl2_actor = tvtk.Actor(position=(3, 0, 0), mapper=cylinder_mapper2, property=p2, orientation=(0, 0, 90))
v.scene.add_actor(cyl2_actor)
# Choose a view angle, and display the figure
mlab.view(90, 45, 7.5, [1.5, 0, 0])
mlab.savefig(filename='cylinders.png')

How to delete and resize matplotlib annotations?

I am using the basic axis.annotate(str(i)) function to show values along the points of my graph. The problem is quite quickly they get to bunched together. So I have two questions: How can I remove an annotation? And how can I make one smaller (font size)?
Here is the reference to the annotation method:
http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.annotate
I have done my research and surprisingly found nothing. Cheers.
axis.annotate(str(i)) returns an axes Annotation object. You need to assign a variable to it and then you manipulate it however you want.
fig, ax = plt.subplots(1,1)
ax.plot(range(5))
text = ax.annotate(xy = (2,2), s='asdf')
# use any set_ function to change all the properties
text.set_fontsize(20)

How to name figure in python

I want to name figures like this:
import matplotlib as plt
for i in range(0,3):
plt.figure('Method%s',%i)
But seems it is not possible this way.
another way I found is using super title but still it does not work:
from pylab import *
for i in range(0,3):
fig = gcf()
fig.suptitle('Method%s',%i)
do you know any solutions?
If you need to use the figures you are going to create, it may be a good move to store them in some kind of data structure. In my example I will use a list and I will give also an example of using later one of the figures that have been instantiated.
Re naming your figures according to a sequence number, you are correct with the general idea but not with the details, as it happend that in plt.figure() to have a user defined name you have to use a keyword argument that is not named name as one could expect, but … num …
figures = [plt.figure(num="Figure n.%d"%(i+1)) for i in range(3)]
# ^^^
...
figures[1].add_axes(...)
...

matplotlib figure canvas name

I have a GUI built using PyQt4 where I use matplotlib (FigureCanvas) to plot several plots using multiple canvases (this is intentional rather than using subplots). for each canvas I use method:
self.canvas_xx.mpl_connect('scroll_event', self.on_mouse_scroll)
where xx represents the iteration of the canvas to get a signal to perform some action. I want to be able to reference the canvas by its name rather than using:
ylabel = event.inaxes.axes.get_ylabel().split(' ')[0]
where I use the longer method of referncing the ylabel name of each graph.
I looked under the event method using: dir(event) and there is a method called "canvas", but there is no apparent method to get the name of the canvas.
Any ideas?
I am not quite sure what you mean by name, but you can get a a reference to the canvas object via
event_canvas = event.inaxes.axes.figure.canvas
and canvas events are hashable, so you can use them as keys in a dict
other_thing = pre_populated_dict[event_canvas]
to keep track of what ever other data you want to about a given canvas in your code.
In python there is, in general, not a clean way to get the names of references to an object (it can be done, but you shouldn't).

Categories

Resources