roflcopter122
roflcopter122

Reputation: 185

Mayavi Integration in PyQt5 possible?

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

Answers (2)

Shuaibin Chang
Shuaibin Chang

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

Ashutosh Rana
Ashutosh Rana

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

Related Questions