2016/01/28

import matplotlibでハング

ここで紹介した方法でmatplotlibやspicyをinstallしたが、実際にmatplotlibを使おうとするとハングする問題が発生。

アクティビティモニターでチェックしてみると"fc-list"なるプロセスがCPUを食ってるよう。

色々調べてみるとここで、同じ現象が取り上げられてる。対策は以下のコマンドを打てとのこと。

$ cd ~/.matplotlib/
$ fc-list 

上をうつと大量のログが出力されたのち、普通に使えるようになった。


2016/01/19

PyQtでvtkをWidget表示

PyQtでvtkの3次元表示ViewをWidgetとしてEmbbedしたい場合の自分メモ

普通はQVTKWidgetPlugin.dll/.soなりをQt Designerのpluginsフォルダに入れる事によって、 Qt DesignerのサイドバーにQVTKWidgetが表示され、それをDrag&DropするだけでLayoutされる!というスマートなやり方が正しいはずなんだが。

ことpython + QVTKWidgetに限ってはなんか以下の理由でできない(なんで?と思うが) (Windows + Anaconda環境と、MacOS + homebrew環境で確認)

  • そもそもQVTKWidgetというモジュールがvtkをpython + qtで使う環境に存在しない。

↓のサイトによるとfrom QVTKWidget import QVTKWidgetに存在しない。from vtk import QVTKWidgetなら存在するとかあるが、これも存在しないと起こられる。

で、色々探してみるとfrom vtk.qt4.QVTKRenderWindowInteractor import QVTKRenderWindowInteractor を使えというサイトを見つけた。

ためしにこれで試すとうまくいくが、もしQt DesignerでQVTKWidgetPlugin.dll/soを使ってDrag&DropでLayoutをすると強制的にQVTKWidgetをloadされちゃうので できない。

なのでQt Desingerのpromote toを使って、

  1. まずWidgetをvtkを配置したい箇所に配置
  2. promote toでpromotionの設定を開く
  3. headerにvtk.qt4.QVTKRenderWindowInteractor.hをpromotion classにQVTKRenderWindowInteractorを書いて追加
  4. 1.で追加したWidgetを右クリックしてpromote to -> QVTKRenderWindowInteractorを選択してpromoteさせる

  5. のQVTKRenderWindowInteractorクラスをPromotion用に追加した所↓

4.の後↓のようになる

という手順を踏むとQt Designderでの変更に対して何もしなくても以下のpythonコードで呼べる。 (C++と.uiファイルが共通化できなかったりというデメリットもあるが、現状これしかない気もする。)

import sys
import vtk

from PyQt4 import QtCore, QtGui, uic

#from ui_mainwindow import Ui_MainWindow

class testPyQt(QtGui.QMainWindow):
    def __init__(self):
        QtGui.QMainWindow.__init__(self)
        Ui_MainWindow = uic.loadUiType("ui_mainwindow.ui", self)[0]

        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        # setup connection
        self.ui.pushButton.clicked.connect(self.onClickButton)

        ### !!! Added from here !!! ###
        self.renderer = vtk.vtkRenderer()
        self.ui.qvtkWidget.GetRenderWindow().AddRenderer(self.renderer)
        self.ui.qvtkWidget.SetInteractorStyle(vtk.vtkInteractorStyleTrackballCamera())

        # Add initial actores
        self.axes = vtk.vtkAxesActor()
        transform = vtk.vtkTransform()
        transform.Translate(0.0, 0.0, 0.0)
        self.axes.SetUserTransform(transform)
        self.axes.GetXAxisCaptionActor2D().GetTextActor().SetTextScaleModeToNone()
        self.axes.GetYAxisCaptionActor2D().GetTextActor().SetTextScaleModeToNone()
        self.axes.GetZAxisCaptionActor2D().GetTextActor().SetTextScaleModeToNone()
        self.axes.SetTotalLength(1, 1, 1)
        self.renderer.AddActor(self.axes)

        self.renderer.ResetCamera()
        ### !!! Added until here !!! ###

    # Added to draw call should be called after open window
    def show(self):
        QtGui.QMainWindow.show(self)
        self.ui.qvtkWidget.Initialize()

    @QtCore.pyqtSlot()
    def onClickButton(self):
        self.ui.label.setText("Pushed!!")

def main():
    app = QtGui.QApplication(sys.argv)
    window = testPyQt()
    window.show()
    sys.exit(app.exec_())

if __name__ == "__main__":
    main()

show()関数をoverrideしているのは、RenderingがWindowが開かないうちに実行されるのを避ける為。例えば、Mac環境でinit内でself.qvtkWidget.Initialize()をやってしまうと以下の様なエラーが大量に出る

ERROR: In /tmp/vtk20151007-9396-c4u0qe/VTK-6.2.0/Rendering/OpenGL/vtkOpenGLPolyDataMapper2D.cxx, line 442
vtkOpenGLPolyDataMapper2D (0x7f9d3c171460): failed after RenderOverlay 1 OpenGL errors detected
  0 : (1286) Invalid framebuffer operation

以下のForumで議論されている内容で、Forumでもshowを明示的に呼んでからRenderしろのような事が書かれている(と思う)。 http://paulklemm.com/zenf/blog/2012/08/20/install-python-together-with-vtk-using-homebrew-for-mountain-lion/ なのでmain()内で呼ばれるshow()の後にInteractorのInitialize()が呼ばれる様にするためにこの構成。


PyQtでGUI:.uiファイルのload

PyQt(PyQt4)でGUIプログラムする自分用メモ

.uiファイル+Qt Designerが使いやすいのもQtのメリットだと思ってるのでpythonからもUIのレイアウトはこれでやりたい。

.uiでレイアウトしてpythonで読み込むには2つ方法があるらしい。

  1. pyuic4を使って.ui -> .pyに変換しimport
  2. uicを使って直接import

1.は.pyに統一できていいが、開発中は毎UI変更の度に変換が必要でめんどい。 2.は変換は不要だが.uiファイルをPackageに含まないといけない(それでもいい場合もある?)。 ↓によると開発中は2.でお手軽に、配布時に1.で変換するみたいのが良いってことかな?
https://riverbankcomputing.com/pipermail/pyqt/2010-September/027970.html

というわけでサンプル作ってみた。

まずQt Designerで簡単なUI(PushButtonを押すと、Labelの文字が変わるというだけ)を作成

.uiファイルをloadして表示するコードが以下。

import sys

from PyQt4 import QtCore, QtGui, uic

# form classを直接importする場合は必要
#from ui_mainwindow import Ui_MainWindow

class testPyQt(QtGui.QMainWindow):
    def __init__(self):
        QtGui.QMainWindow.__init__(self)
        # form classを直接importする場合は↓の行が不要になる
        Ui_MainWindow = uic.loadUiType("ui_mainwindow.ui", self)[0]

        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        # setup connection
        self.ui.pushButton.clicked.connect(self.onClickButton)

    @QtCore.pyqtSlot()
    def onClickButton(self):
        self.ui.label.setText("Pushed!!")

def main():
    app = QtGui.QApplication(sys.argv)
    window = testPyQt()
    window.show()
    sys.exit(app.exec_())

if __name__ == "__main__":
    main()

testPyQtクラスのself.uiにform class(C++ではUi::MainWindowClassとかでmoc_*.cppファイルに生成されてたやつだと思う)をnewする。

検索するとtestPyQtクラス自体をform classから継承する例や、self.uiにuic.loadUi()で生成されたQMainWindow(or QWidget)を直接showするコード(下記)もあったというか結構多い。

前者はUIと制御コードが一緒のclassになっちゃうので避けたい実装、後者はtestPyQtでQMainWindowを継承している意味がわかんなくなるので、その辺のやり方を避けると上のやり方に落ち着きそう。

class testPyQt(QtGui.QMainWindow):
    def __init__(self):
    # uic.loadUi returns QMainWindow
    self.ui = uic.loadUi("ui_mainwindow.ui")
    self.ui.show()

def main():
    app = QtGui.QApplication(sys.argv)
    window = testPyQt()
    # window.show() # ここでshowしない
    sys.exit(app.exec_())

結果、こんな感じにできる。