I want to get a multiscene layout described in https://docs.enthought.com/mayavi/mayavi/auto/example_multiple_mlab_scene_models.html
import numpy as np
from traits.api import HasTraits, Instance, Button, \
on_trait_change
from traitsui.api import View, Item, HSplit, Group
from mayavi import mlab
from mayavi.core.ui.api import MlabSceneModel, SceneEditor
class MyDialog(HasTraits):
scene1 = Instance(MlabSceneModel, ())
scene2 = Instance(MlabSceneModel, ())
button1 = Button('Redraw')
button2 = Button('Redraw')
#on_trait_change('button1')
def redraw_scene1(self):
self.redraw_scene(self.scene1)
#on_trait_change('button2')
def redraw_scene2(self):
self.redraw_scene(self.scene2)
def redraw_scene(self, scene):
# Notice how each mlab call points explicitly to the figure it
# applies to.
mlab.clf(figure=scene.mayavi_scene)
x, y, z, s = np.random.random((4, 100))
mlab.points3d(x, y, z, s, figure=scene.mayavi_scene)
# The layout of the dialog created
view = View(HSplit(
Group(
Item('scene1',
editor=SceneEditor(), height=250,
width=300),
'button1',
show_labels=False,
),
Group(
Item('scene2',
editor=SceneEditor(), height=250,
width=300, show_label=False),
'button2',
show_labels=False,
),
),
resizable=True,
)
m = MyDialog()
m.configure_traits()
Each scene has to render a separate volume object.
I have provided a custom redraw_scene function with
def redraw_scene(self, scene):
# Notice how each mlab call points explicitly to the figure it
# applies to.
mlab.clf(figure=scene.mayavi_scene)
s = np.random.random((100, 100, 100))
mlab.pipeline.volume(mlab.pipeline.scalar_field(s), figure=scene.mayavi_scene)
but ended up getting both volumes rendered on the second scene.
I have also tried the setup with a separate engine per scene but it yields the same result.
How I do get volume renders in separate scenes with Mayavi?
Related
I am trying to remove an image from a figure and release the memory. when colorbar is not added for the image, memory can be released successfully, however, if colorbar is added, it fails. In the demo-code bellow:
click push button Add ColorBar will add a color bar for one image in the figure.
click push button remove will remove one image(and the related colorbar) from the figure.
each time i remove the image, the colorbar related is also removed, so i don't know why the memory recycle fails, I guess there must be some extra reference to the image when add a colorbar to it, which fails the memory recycle.
import numpy as np
from PyQt5 import QtWidgets
from memory_profiler import profile
import matplotlib
from matplotlib.figure import Figure
import matplotlib.cm as cm
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
from matplotlib.axes._axes import Axes
matplotlib.use("Qt5Agg")
class MplCanvas(FigureCanvasQTAgg):
def __init__(self, parent=None, width=5, height=4, dpi=100):
self.fig = Figure(figsize=(width, height), dpi=dpi)
self.axe = self.fig.add_subplot(1, 1, 1, label='good')
super().__init__(self.fig)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
layout = QtWidgets.QVBoxLayout()
self.canvas = MplCanvas(self, width=5, height=4, dpi=100)
self.axe = self.canvas.axe
layout.addWidget(self.canvas)
self.pushButton_addColorBar = QtWidgets.QPushButton('Add ColorBar')
layout.addWidget(self.pushButton_addColorBar)
self.pushButton_remove = QtWidgets.QPushButton('remove')
layout.addWidget(self.pushButton_remove)
widget = QtWidgets.QWidget()
widget.setLayout(layout)
self.setCentralWidget(widget)
self.pushButton_remove.clicked.connect(self.removeImage)
self.pushButton_addColorBar.clicked.connect(self.createColorBar)
self.pcolormesh_test()
def pcolormesh_test(self):
"""add two images"""
delta = 0.01
x = y = np.arange(-3.0, 3.0, delta)
X, Y = np.meshgrid(x, y)
Z1 = np.exp(-X ** 2 - Y ** 2)
Z2 = np.exp(-(X - 1) ** 2 - (Y - 1) ** 2)
Z = (Z1 - Z2) * 2
im = self.axe.pcolormesh(X, Y, Z, cmap=cm.viridis, shading='auto')
im.set_clim(vmax=np.amax(Z), vmin=np.amin(Z))
Zx = (Z1 + Z2) * 2
imx = self.axe.pcolormesh(X, Y, Zx, cmap=cm.Blues, shading='auto')
imx.set_clim(vmax=np.amax(Zx), vmin=np.amin(Zx))
def createColorBar(self):
""" to create a color bar for an image. """
axe = self.axe
fig = axe.get_figure()
images = self.getImages(axe)
for image in images:
if not image.colorbar: # color bar doesn't exist
inset_axe = axe.inset_axes([1.0, 0, 0.05, 1], transform=axe.transAxes)
fig.colorbar(image, ax=axe, cax=inset_axe)
break # each trigger create one colorbar for one image
self.reDraw()
#profile
def removeImage(self, checked):
"""
Usage:
* each trigger remove one image
"""
images = self.getImages(self.axe)
# print(f'images={images}')
if images:
image = images[-1]
color_bar = image.colorbar
if color_bar:
color_bar.remove()
del color_bar
# remove image
image.remove()
del image
self.reDraw()
def getImages(self, axe: Axes):
"""to obtain the image list in the axe"""
images = []
images.extend(axe.images)
images.extend(axe.collections)
return images
def reDraw(self):
self.canvas.draw_idle()
self.canvas.flush_events()
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
I have found the solution, and post an answer to help.
we need to add gc.collect() at the end of removeImage() method. then the memory can be reclaimed when the image is removed.
In update_plot function I added print statement to check if I am getting correct data after interacting with scene, I can see I am getting correct value but plot itself is not updated. I am not where I am doing wrong. I think I am doing something wrong when I pass data back to scaler_field
from traits.api import HasTraits, Range, Instance, \
on_trait_change
from traitsui.api import View, Item, HGroup
from tvtk.pyface.scene_editor import SceneEditor
from mayavi.tools.mlab_scene_model import \
MlabSceneModel
from mayavi.core.ui.mayavi_scene import MayaviScene
from mayavi import mlab
class Visualization(HasTraits):
mySlice = Range(0, 400, 100) # slice number
scene = Instance(MlabSceneModel, ())
def __init__(self):
HasTraits.__init__(self)
data = InlinemySlice(self.mySlice) # call new data
self.x_source = self.scene.mlab.pipeline.scalar_field(data)
self.plot = self.scene.mlab.pipeline.image_plane_widget(self.x_source, plane_orientation='x_axes', colormap='Greys', vmin=-0.020505040884017944 ,vmax=0.020505040884017944)
#on_trait_change('mySlice')
def update_plot(self):
x = InlinemySlice(self.mySlice)
print(x)
y_source = mlab.pipeline.scalar_field(x)
self.plot.mlab_source.trait_set(y_source)
# the layout of the dialog created
view = View(Item('scene', editor=SceneEditor(scene_class=MayaviScene),
height=500, width=600, show_label=False),
HGroup( 'mySlice' ),
)
visualization = Visualization()
visualization.configure_traits()
I'm following the script given at the official mayaVI site (Multiple mlab scene models example), and would like to use the sync_camera command to sync the two figures together within a qt GUI (just as shown), such that any rotation/zoom, etc in one figure automatically rotates/zooms, etc the other in the exact same manner, at the same time.
The sync_camera command is written about briefly on another official mayaVI page Figure handling functions, but I haven't been able to find much on its proper use to utilize successfully within the class hierarchy.
Does anyone have any experience with this procedure or advice?
import numpy as np
from traits.api import HasTraits, Instance, Button, \
on_trait_change
from traitsui.api import View, Item, HSplit, Group
from mayavi import mlab
from mayavi.core.ui.api import MlabSceneModel, SceneEditor
class MyDialog(HasTraits):
scene1 = Instance(MlabSceneModel, ())
scene2 = Instance(MlabSceneModel, ())
button1 = Button('Redraw')
button2 = Button('Redraw')
#on_trait_change('button1')
def redraw_scene1(self):
self.redraw_scene(self.scene1)
#on_trait_change('button2')
def redraw_scene2(self):
self.redraw_scene(self.scene2)
def redraw_scene(self, scene):
# Notice how each mlab call points explicitely to the figure it
# applies to.
mlab.clf(figure=scene.mayavi_scene)
x, y, z, s = np.random.random((4, 100))
mlab.points3d(x, y, z, s, figure=scene.mayavi_scene)
# The layout of the dialog created
view = View(HSplit(
Group(
Item('scene1',
editor=SceneEditor(), height=250,
width=300),
'button1',
show_labels=False,
),
Group(
Item('scene2',
editor=SceneEditor(), height=250,
width=300, show_label=False),
'button2',
show_labels=False,
),
),
resizable=True,
)
m = MyDialog()
m.configure_traits()
The solution is to not use the 2-figure-in-1 method (as originally posted), but to create 2 separate figures. For my needs, I've rewritten the initial code such that each figure is in it's own class, and then simply placed them in a new frame side by side. I don't think using the sync_camera function is possible without such a separation, since it requires two separate figures as inputs. The result is basically identical. I successfully implemented the sync_camera function as follows:
import sys, os, time
import numpy as np
os.environ['ETS_TOOLKIT'] = 'qt4'
from pyface.qt import QtGui, QtCore
from traits.api import HasTraits, Instance, on_trait_change, Str, Float, Range
from traitsui.api import View, Item, HSplit, Group
from mayavi import mlab
from mayavi.core.api import PipelineBase, Engine
from mayavi.core.ui.api import MayaviScene, MlabSceneModel, SceneEditor
class Mayavi1(HasTraits):
scene = Instance(MlabSceneModel, ())
#on_trait_change('scene.activated')
def update_plot(self):
Mayavi1.fig1 = mlab.figure(1)
self.scene.mlab.clf(figure=Mayavi1.fig1)
x, y, z, s = np.random.random((4, 100))
splot = self.scene.mlab.points3d(x, y, z, s, figure=Mayavi1.fig1)
#splot.actor.actor.scale = np.array([25,25,25]) #if plot-types different
view = View(Item('scene', editor=SceneEditor(scene_class=MayaviScene),
height=300, width=300, show_label=False),
resizable=True
)
class Mayavi2(HasTraits):
scene = Instance(MlabSceneModel, ())
#on_trait_change('scene.activated')
def update_plot(self):
Mayavi2.fig2 = mlab.figure(2)
self.scene.mlab.clf(figure=Mayavi2.fig2)
x, y, z, s = np.random.random((4, 100))
cplot = self.scene.mlab.points3d(x, y, z, s, figure=Mayavi2.fig2)
#cplot.actor.actor.position = np.array([1,1,1]) #if plot-types different
view = View(Item('scene', editor=SceneEditor(scene_class=MayaviScene),
height=300, width=300, show_label=False),
resizable=True
)
class P1(QtGui.QWidget):
def __init__(self, parent=None):
super(P1, self).__init__(parent)
layout = QtGui.QGridLayout(self)
layout.setContentsMargins(20,20,20,20) #W,N,E,S
layout.setSpacing(10)
self.visualization1 = Mayavi1()
self.ui1 = self.visualization1.edit_traits(parent=self, kind='subpanel').control
layout.addWidget(self.ui1, 0, 0, 1, 1)
self.ui1.setParent(self)
self.visualization2 = Mayavi2()
self.ui2 = self.visualization2.edit_traits(parent=self, kind='subpanel').control
layout.addWidget(self.ui2, 0, 2, 1, 1)
self.ui2.setParent(self)
mlab.sync_camera(self.visualization1,self.visualization2)
mlab.sync_camera(self.visualization2,self.visualization1)
#self.visualization1.scene.mlab.view(0,0,10,[1,1,1])
class Hierarchy(QtGui.QMainWindow):
def __init__(self, parent=None):
super(Hierarchy, self).__init__(parent)
self.setGeometry(50, 50, 400, 400) #(int x, int y, int w, int h)
self.gotoP1()
def gotoP1(self):
self.P1f = P1(self)
self.setWindowTitle("Page1")
self.setCentralWidget(self.P1f)
self.show()
if __name__ == '__main__':
app = QtGui.QApplication.instance()
#app = QtGui.QApplication(sys.argv)
w = Hierarchy()
sys.exit(app.exec_())
However, in my own version, I'm using two different data sources within each plot (one a scatter plot and the other a contour plot, with the contour plot origin of interest different from the scatter plot), and because of the camera connection, neither one is on screen at the same time as the other (native coordinates distinct in both).
Thus, if you're only seeing one of the 3d objects in frame at a time, adjust the positions within the def update_plot(self) for either figure until they are both viewed on the screen at the same time. This can be done via such commands as:
splot.actor.actor.scale = np.array([25,25,25]) #with splot for fig1
cplot.actor.actor.position = np.array([-64,-64,-64]) #with cplot for fig2
I highly suggest actually going into the mayaVI pipeline (with the red light clicked to see the output code in real-time) to adjust your plots as needed. If anyone needs any further help with this down the road, please let me know.
I was wondering if anyone had an idea as to why the code below does not display a graph with a line in it after the button on the GUI is pressed. I would like to create a program that executes a long list of commands after a set of data is imported by clicking a button. One of these commands would be to display the spectral data on a graph within the same window. Here is what I have so far:
# import modules that I'm using
import matplotlib
matplotlib.use('TKAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
import matplotlib.pyplot as pltlib
import Tkinter
from Tkinter import *
import numpy as np
import scipy as sc
#import matplotlib.pyplot as pltlib
# lmfit is imported becuase parameters are allowed to depend on each other along with bounds, etc.
from lmfit import minimize, Parameters, Minimizer
#Make object for application
class App_Window(Tkinter.Tk):
def __init__(self,parent):
Tkinter.Tk.__init__(self,parent)
self.parent = parent
self.initialize()
def initialize(self):
button = Tkinter.Button(self,text="Open File",command=self.OnButtonClick).pack(side=Tkinter.TOP)
self.canvasFig=pltlib.figure(1)
Fig = matplotlib.figure.Figure(figsize=(5,4),dpi=100)
FigSubPlot = Fig.add_subplot(111)
x=[]
y=[]
self.line1, = FigSubPlot.plot(x,y,'r-')
self.canvas = matplotlib.backends.backend_tkagg.FigureCanvasTkAgg(Fig, master=self)
self.canvas.show()
self.canvas.get_tk_widget().pack(side=Tkinter.TOP, fill=Tkinter.BOTH, expand=1)
self.canvas._tkcanvas.pack(side=Tkinter.TOP, fill=Tkinter.BOTH, expand=1)
self.resizable(True,False)
self.update()
def refreshFigure(self,x,y):
self.line1.set_xdata(x)
self.line1.set_ydata(y)
self.canvas.draw()
def OnButtonClick(self):
# file is opened here and some data is taken
# I've just set some arrays here so it will compile alone
x=[]
y=[]
for num in range(0,1000):x.append(num*.001+1)
# just some random function is given here, the real data is a UV-Vis spectrum
for num2 in range(0,1000):y.append(sc.math.sin(num2*.06)+sc.math.e**(num2*.001))
X = np.array(x)
Y = np.array(y)
self.refreshFigure(X,Y)
if __name__ == "__main__":
MainWindow = App_Window(None)
MainWindow.mainloop()
That is because the range of xaxis & yaxis doesn't change to new data's range, change your refreshFigure as following:
def refreshFigure(self,x,y):
self.line1.set_data(x,y)
ax = self.canvas.figure.axes[0]
ax.set_xlim(x.min(), x.max())
ax.set_ylim(y.min(), y.max())
self.canvas.draw()
Dear programmming communauty,
I am trying to perform a "interactive plot" based on Tkinter and pylab.plot in order to plot 1D values. The abssissa are a 1D numpy array x and the ordonates values are in a multidimension array Y, eg.
import numpy
x = numpy.arange(0.0,3.0,0.01)
y = numpy.sin(2*numpy.pi*x)
Y = numpy.vstack((y,y/2))
I want to display y or y/2 (the elements of Y matrix) according to x and change between them with 2 buttons left and right (in order to go to more complex cases). Usually I create some functions like the following to plot graphs.
import pylab
def graphic_plot(n):
fig = pylab.figure(figsize=(8,5))
pylab.plot(x,Y[n,:],'x',markersize=2)
pylab.show()
To add two buttons to change the value of nparameter, I have tried this without success :
import Tkinter
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
class App:
def __init__(self,master):
# Create a container
frame = Tkinter.Frame(master)
frame.pack()
# Create 2 buttons
self.button_left = Tkinter.Button(frame,text="<",command=self.decrease)
self.button_left.pack(side="left")
self.button_right = Tkinter.Button(frame,text=">",command=self.increase)
self.button_right.pack(side="left")
self.canvas = FigureCanvasTkAgg(fig,master=self)
self.canvas.show()
def decrease(self):
print "Decrease"
def increase(self):
print "Increase"
root = Tkinter.Tk()
app = App(root)
root.mainloop()
Can someone help me to understand how to perform such kind of feature ? Many thanks.
To change the y-values of the line, save the object that's returned when you plot it (line, = ax.plot(...)) and then use line.set_ydata(...). To redraw the plot, use canvas.draw().
As a more complete example based on your code:
import Tkinter
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
class App:
def __init__(self, master):
# Create a container
frame = Tkinter.Frame(master)
# Create 2 buttons
self.button_left = Tkinter.Button(frame,text="< Decrease Slope",
command=self.decrease)
self.button_left.pack(side="left")
self.button_right = Tkinter.Button(frame,text="Increase Slope >",
command=self.increase)
self.button_right.pack(side="left")
fig = Figure()
ax = fig.add_subplot(111)
self.line, = ax.plot(range(10))
self.canvas = FigureCanvasTkAgg(fig,master=master)
self.canvas.show()
self.canvas.get_tk_widget().pack(side='top', fill='both', expand=1)
frame.pack()
def decrease(self):
x, y = self.line.get_data()
self.line.set_ydata(y - 0.2 * x)
self.canvas.draw()
def increase(self):
x, y = self.line.get_data()
self.line.set_ydata(y + 0.2 * x)
self.canvas.draw()
root = Tkinter.Tk()
app = App(root)
root.mainloop()