Reputation: 185
Is it possible to integrate a mayavi 3d plot into a gui which was made with pyqt5? In mayavi documentation I found this: http://docs.enthought.com/mayavi/mayavi/building_applications.html but when I run the code I get an error: "RuntimeError: No traitsui.toolkits plugin found for toolkit qt5".
Here is the code:
# First, and before importing any Enthought packages, set the ETS_TOOLKIT
# environment variable to qt4, to tell Traits that we will use Qt.
import os
os.environ['ETS_TOOLKIT'] = 'qt4'
# By default, the PySide binding will be used. If you want the PyQt bindings
# to be used, you need to set the QT_API environment variable to 'pyqt'
#os.environ['QT_API'] = 'pyqt'
# To be able to use PySide or PyQt4 and not run in conflicts with traits,
# we need to import QtGui and QtCore from pyface.qt
from pyface.qt import QtGui, QtCore
# Alternatively, you can bypass this line, but you need to make sure that
# the following lines are executed before the import of PyQT:
# import sip
# sip.setapi('QString', 2)
from traits.api import HasTraits, Instance, on_trait_change
from traitsui.api import View, Item
from mayavi.core.ui.api import MayaviScene, MlabSceneModel, \
SceneEditor
################################################################################
#The actual visualization
class Visualization(HasTraits):
scene = Instance(MlabSceneModel, ())
@on_trait_change('scene.activated')
def update_plot(self):
# This function is called when the view is opened. We don't
# populate the scene when the view is not yet open, as some
# VTK features require a GLContext.
# We can do normal mlab calls on the embedded scene.
self.scene.mlab.test_points3d()
# the layout of the dialog screated
view = View(Item('scene', editor=SceneEditor(scene_class=MayaviScene),
height=250, width=300, show_label=False),
resizable=True # We need this to resize with the parent widget
)
################################################################################
# The QWidget containing the visualization, this is pure PyQt4 code.
class MayaviQWidget(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
layout = QtGui.QVBoxLayout(self)
layout.setContentsMargins(0,0,0,0)
layout.setSpacing(0)
self.visualization = Visualization()
# If you want to debug, beware that you need to remove the Qt
# input hook.
#QtCore.pyqtRemoveInputHook()
#import pdb ; pdb.set_trace()
#QtCore.pyqtRestoreInputHook()
# The edit_traits call will generate the widget to embed.
self.ui = self.visualization.edit_traits(parent=self,
kind='subpanel').control
layout.addWidget(self.ui)
self.ui.setParent(self)
if __name__ == "__main__":
# Don't create a new QApplication, it would unhook the Events
# set by Traits on the existing QApplication. Simply use the
# '.instance()' method to retrieve the existing one.
app = QtGui.QApplication.instance()
container = QtGui.QWidget()
container.setWindowTitle("Embedding Mayavi in a PyQt4 Application")
# define a "complex" layout to test the behaviour
layout = QtGui.QGridLayout(container)
# put some stuff around mayavi
label_list = []
for i in range(3):
for j in range(3):
if (i==1) and (j==1):continue
label = QtGui.QLabel(container)
label.setText("Your QWidget at (%d, %d)" % (i,j))
label.setAlignment(QtCore.Qt.AlignHCenter|QtCore.Qt.AlignVCenter)
layout.addWidget(label, i, j)
label_list.append(label)
mayavi_widget = MayaviQWidget(container)
layout.addWidget(mayavi_widget, 1, 1)
container.show()
window = QtGui.QMainWindow()
window.setCentralWidget(container)
window.show()
# Start the main event loop.
app.exec_()
Upvotes: 0
Views: 2980
Reputation: 127
Ashutosh's answer works, but for myself I want to update the 3D image frequently and also be able to adjust the brightness and contrast of the 3D image. I figured out the first part, but the B&C part has been a headache for me. Here's my code that uses PyQt5 instead of pyface:
from traits.api import HasTraits, Instance, on_trait_change
from traitsui.api import View, Item
from mayavi.core.ui.api import MayaviScene, MlabSceneModel, \
SceneEditor
from mayavi import mlab
import os
import os
from PyQt5.QtWidgets import QMainWindow, QFileDialog, QWidget, QVBoxLayout
import PyQt5.QtCore as qc
import numpy as np
class Visualization(HasTraits):
scene = Instance(MlabSceneModel, ())
def __init__(self, data):
HasTraits.__init__(self)
self.data = data
def update_contrast(self, low, high):
pass
# calculate data according to low and high
# self.plot.mlab_source.scalars = data-low+1000-high
# M=np.max(self.plot.mlab_source.scalars)
# self.plot.current_range=(low,M*high/1000)
# # print(low, high)
# print(self.plot.current_range)
def update_data(self, data):
self.plot.mlab_source.scalars = data
# print(data.shape)
# print(self.plot.current_range)
# print(np.max(self.plot.mlab_source.scalars))
# print(data[1,1,:])
self.plot.current_range=(2, self.plot.current_range[1]*0.1)
# print(self.plot.current_range)
@on_trait_change('scene.activated')
def initial_plot(self):
self.plot = mlab.pipeline.volume(mlab.pipeline.scalar_field(self.data))
self.scene.background=(0,0,0)
# print(self.plot.current_range)
# self.plot.lut_manager.lut_mode = 'grey'
# a=self.plot.lut_manager
# print(self.plot.mlab_source.all_trait_names())
# print(self.plot.all_trait_names())
# the layout of the dialog screated
view = View(Item('scene', editor=SceneEditor(scene_class=MayaviScene),
height=250, width=300, show_label=False),
resizable=True # We need this to resize with the parent widget
)
# The QWidget containing the visualization, this is pure PyQt4 code.
class MayaviQWidget(QWidget):
def __init__(self, data = None):
super().__init__()
data = np.random.random((200,1000,300))
self.visualization = Visualization(data)
layout = QVBoxLayout(self)
# layout.setContentsMargins(0,0,0,0)
# layout.setSpacing(0)
# The edit_traits call will generate the widget to embed.
self.ui = self.visualization.edit_traits(parent=self,
kind='subpanel').control
layout.addWidget(self.ui)
# self.setLayout(layout)
# self.ui.setParent(self)
Here's how I call this mayaviQwidget, Ui_MainWindow() is my GUI made from Qtdesigner, it has a tmp_label which is going to be replaced by mayaviQwidget:
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
if maya_installed:
self.ui.mayavi_widget = MayaviQWidget()
self.ui.mayavi_widget.setMinimumSize(qc.QSize(100, 100))
self.ui.mayavi_widget.setMaximumSize(qc.QSize(600, 600))
self.ui.mayavi_widget.setObjectName("XYZView")
# self.ui.verticalLayout_2.removeWidget(self.ui.tmp_label)
# self.ui.verticalLayout_2.addWidget(self.ui.mayavi_widget)
self.ui.verticalLayout_2.replaceWidget(self.ui.tmp_label, self.ui.mayavi_widget)
Basically after searching for the traits of every variable I found the trait accounting for the 3D data array is self.plot.mlab_source.scalars
. So I just have to update that trait to update the image. However, I couldn't find the trains responsible for vmax and vmin. I found that if I change self.plot.current_range
it will change the B&C of the 3D image, but not in a way I can understand. For example, if I set the upper bound to be half the max value of the 3D array, the image remain unchanged. Another example, if I slightly increase the lower bound of current_range the image changes dramastically, but if I change the lower bound back to its original value the image remain unchanged.
Currently my solution is to set current_range to be [0, 0.2*max of 3D array] in update_data(), that way the B&C look OK. But I won't be able to adjust it to make it look better.
Upvotes: 0
Reputation: 133
Yes, It is possible to integrate a mayavi 3d plot into a gui which was made with pyqt5. Not only the plot you can also show animations! Almost everything whatever you do with a separate Mayavi window can be done in PyQt5 Gui by embedding mayavi scene anywhere in your GUI.
If your code is exactly the same as this link : Qt embedding example. Then try to run this code in separate file, it should work. If not you have to reinstall your libraries properly.
You can refer the code(simpler version) below to embed mayavi in your GUI:
from PyQt5 import QtWidgets
import os
import numpy as np
from numpy import cos
from mayavi.mlab import contour3d
os.environ['ETS_TOOLKIT'] = 'qt4'
from pyface.qt import QtGui, QtCore
from traits.api import HasTraits, Instance, on_trait_change
from traitsui.api import View, Item
from mayavi.core.ui.api import MayaviScene, MlabSceneModel, SceneEditor
## create Mayavi Widget and show
class Visualization(HasTraits):
scene = Instance(MlabSceneModel, ())
@on_trait_change('scene.activated')
def update_plot(self):
## PLot to Show
x, y, z = np.ogrid[-3:3:60j, -3:3:60j, -3:3:60j]
t = 0
Pf = 0.45+((x*cos(t))*(x*cos(t)) + (y*cos(t))*(y*cos(t))-(z*cos(t))*(z*cos(t)))
obj = contour3d(Pf, contours=[0], transparent=False)
view = View(Item('scene', editor=SceneEditor(scene_class=MayaviScene),
height=250, width=300, show_label=False),
resizable=True )
class MayaviQWidget(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
layout = QtGui.QVBoxLayout(self)
layout.setContentsMargins(0,0,0,0)
layout.setSpacing(0)
self.visualization = Visualization()
self.ui = self.visualization.edit_traits(parent=self,
kind='subpanel').control
layout.addWidget(self.ui)
self.ui.setParent(self)
#### PyQt5 GUI ####
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
## MAIN WINDOW
MainWindow.setObjectName("MainWindow")
MainWindow.setGeometry(200,200,1100,700)
## CENTRAL WIDGET
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
MainWindow.setCentralWidget(self.centralwidget)
## GRID LAYOUT
self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)
self.gridLayout.setObjectName("gridLayout")
## BUTTONS
self.button_default = QtWidgets.QPushButton(self.centralwidget)
self.button_default.setObjectName("button_default")
self.gridLayout.addWidget(self.button_default, 0, 0, 1,1)
self.button_previous_data = QtWidgets.QPushButton(self.centralwidget)
self.button_previous_data.setObjectName("button_previous_data")
self.gridLayout.addWidget(self.button_previous_data, 1, 1, 1,1)
## Mayavi Widget 1
container = QtGui.QWidget()
mayavi_widget = MayaviQWidget(container)
self.gridLayout.addWidget(mayavi_widget, 1, 0,1,1)
## Mayavi Widget 2
container1 = QtGui.QWidget()
mayavi_widget = MayaviQWidget(container1)
self.gridLayout.addWidget(mayavi_widget, 0, 1,1,1)
## SET TEXT
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "Simulator"))
self.button_default.setText(_translate("MainWindow","Default Values"))
self.button_previous_data.setText(_translate("MainWindow","Previous Values"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
app.setStyle('Fusion')
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
Try to run this code in a dedicated console. Hope this will help you.
Upvotes: 3